Nuxt.js for SSR+ Express for API + TypeScript + Firebase = Profit!
tl;dr: Here’s an example repo of Nuxt.js & Express using TypeScript, deployable to Firebase Cloud Functions and Hosting, set up for both development and production use.
https://github.com/williamchong/nuxtjs-express-firebase-typescript
Last time I shared how to deploy Nuxt.js on Firebase Cloud Functions. However some common use cases were not covered. What if I want to run a separate backend API server, instead of using Nuxt.js server middleware? Firebase Cloud Function supports TypeScript, what if I want to migrate to TypeScript?
In fact, in LikeCoin we are also trying to migrate our JavaScript codebase to TypeScript. Need not worry, this guide will get you covered.
Development Setup
Development Folder Structure
We will begin with a simple create-nuxt-app
template withaxios
module and TypeScript support. However, I will modify the folder structure a bit, moving all Nuxt.js source file into /src
, and adding a /src/server
and /src/server/api
for placing our APIs. It should look like this.
The reason for placing /server
inside /src
is that I want to run both the Nuxt builder and Express API server in a single npm command, allowing hot reload to trigger on file change in either directory.
Development Server
Inside /api
are the server API routes that should be customized to your backend business logic. In this sample, they are just stub and will the actual code be omitted. Here I want to focus on /src/server/index.ts
, which is the main Express instance of this development setup.
https://gist.github.com/williamchong/4baeeef45bf942bb117718d137f56028#file-index-ts
I have used new programmatic render syntax for Nuxt renderer/builder, loadNuxt
and build
. The advantage of using loadNuxt
is that you won’t have to directly require()
the nuxt.config.js
config file. This solves the issue that if your config file uses import
or other TypeScript syntax, it needed additional transpile to commonjs. The API routes are all included inside the /api/index.ts
as an Express router and are served under /api
.
By using this configuration, the development server will trigger a Nuxt build if run in a development environment. By running the server, both the /api
endpoints and the Nuxt renderer with being served in an all-in-one configuration.
Extra tips:
Like I mentioned in my previous article, using the nuxt-start
library for a lighter weight production start-up would also be a good idea for cloud function. However, to keep this example simple I will be using nuxt
instead.
Since Nuxt middleware is not yet properly typed, in line 5 I used @ts-ignore
, You can use other mitigation like declare module 'nuxt'
. Also, there is an export {}
at the end to ignore unwanted TypeScript warning.
Development Server TypeScript config
Speaking of TypeScript warning, we also want a separate tsconfig.json
for this Express server. This is because the one provided in create-nuxt-app
the template includes the DOM
lib used for a browser environment. This will emit many unwanted warnings when running the Express server in node.js serve environment. The tsconfig.json for the server can be found here, and the main difference is to remove DOM
from lib
. However you can also set up other rules according to your needs, should you have some rules that differ in your Nuxt and Express environment.
Development package.json
The last part is how to run the above development server. One good developer experience for Nuxt is that when running, it provides hot-reload whenever we changed our code, and we definitely want to keep that trait in our new setup. We will be using ts-watch
which is basically node-mon
+ tsc
built together. This will provide hot-reload for the server-side code. When we are running the Nuxt builder and renderer middleware in development mode, they will provide the client-side hot reload just like running the default nuxt-ts
command.
To archive this behavior, we will use these package.json
scripts.
https://gist.github.com/williamchong/37ed6d7ff1d519830dee98df2e05b0b7#file-package-json
The major changes I have made are splitting up the build
script into build:client
and build:server
. Also the dev
script is changed to using tsc-watch
. The build:server
script is a standard one using tsc
to compile Typescript into JavaScript. The dev
script watches the server
folder for any change in server code, rebuild the code into the output folder lib
, then run the server code.
One point to notice is that any change in server code will reload the server instance and thus the Nuxt builder, which will trigger a full rebuild. So I recommend finishing the server API development first before drilling deep into the fancy frontend side of your application.
The above files are the main components of a dev setup. By now you should be able to run just run npm run dev
and enjoy a front-backend hot reload server.
Production Setup
Production Folder Structure
After you complete the development of your Nuxt application. It is time to bring it to production. As mentioned we will be using Firebase hosting for static assets, and cloud functions for running SSR renderer. In addition, we will be serving our Express API server with a cloud function too.
Let us begin by adding two new folders /functions
and /public_base
. /functions
is for housing our cloud functions, and /public_base
is the folder for your static asset. We will later merge this folder with the static scripts generated by Nuxt
builder together into the final /public
folder to be put on hosting.
Production Functions setup
Like in the dev environment, we will be putting our TypeScript source code into src
and later build them into JavaScript in lib
.
Inside the /functions/src
, we have ssrapp
which houses the Nuxt SSR renderer functions. We also have apiHttp
which houses the API Express instance in index.ts
and api
which is a symlink to /src/server/api
. A symlink was used so that we can guarantee the consistency of server code in the dev environment and production deployment without any extra synchronization overhead.
https://gist.github.com/williamchong/8c82829084f5090210b5ca22671a5e3b#file-index-ts
The difference between the index.ts
used in dev and production environ for API is that Nuxt builder and renderer is not included thus nuxt
not need to be loaded, and helmet
was used to improve security. This separation of API and frontend rendering functions would be good for API performance. Also, it is useful if you have any other non-Nuxt.js frontend that you plan to share this set of API with.
Production Firebase config setup
Finally, the last step to wrap all things up ready for deployment is the firebase.json
used by the Firebase command line. We can deploy the app with a simple firebase deploy
command with the following configuration.
https://gist.github.com/williamchong/1a01e4e79a2d26073bf26b1c995ec08e#file-firebase-json
Like in my previous article, we will first build the production build for Nuxt.js frontend. In functions
state we will build the TypeScript into JavaScript using tsc
, then copy the nuxt
build output for SSR function deployment. In hosting
we also copy the static asset part of the nuxt
output and put together with /public_base
into /public
for upload.
Also, we have defined redirection rules for hosting, anything under /api
is directed to the API function, otherwise, the frontend SSR function will handle the rest. Since an exact match on hosting
asset has higher priority then the rewrites, the static asset in/public
will be served from CDN instead of cloud functions if the URL is an exact match.
As you might have noticed, using TypeScript actually made the functions
build process simpler!
Deploy to production
The final step would be running the Firebase deploy command
firebase deploy
Then the Nuxt.js app and Express API server will be deployed to Firebase automagically.
The above instructions only cover the essential steps and concepts. For a complete config/code example, please visit my sample repo here. Also here is a sample site deployed to Firebase.
Extra
If you encounter an error like this
HTTP Error: 400, Billing account for project ‘xxxxxx’ is not found. Billing must be enabled for activation of service(s) ‘cloudbuild.googleapis.com,containerregistry.googleapis.com’ to proceed.
You probably are on a free tier plan for Firebase and you are trying to deploy a Node.js 10 cloud function. Setting engines to Node.js 8 in cloud functions’ package.json
should solve your issue.
"engines": { "node": "8"}
However, I recommend using an instance with Node.js version 10 if you do have a billing account setup, as version 8 is already deprecated.
#cloud functions express #nuxt express #nuxt express template #nuxt js typescript