250 lines
10 KiB
Markdown
250 lines
10 KiB
Markdown
---
|
||
title: 'Bundling a Node API with Fastify, esbuild, and Nx'
|
||
slug: 'bundling-a-node-api-with-fastify-esbuild-and-nx'
|
||
authors: ['Jack Hsu']
|
||
cover_image: '/blog/images/2023-02-28/PADY_RKrkXj39p4nj79ESw.avif'
|
||
tags: [nx, tutorial]
|
||
description: Build and deploy a Node.js API with Fastify, Nx, esbuild, Docker, and Fly.io.
|
||
---
|
||
|
||
There are many decisions to make when it comes to building a Node API. There are a variety of frameworks to choose from (Express, Fastify, Koa, etc.), and a million different ways to build and deploy the application.
|
||
|
||
In this article, I'll show you the easiest way to go from zero to production by using Nx to create a Node API project.
|
||
|
||
We'll be using [Fastify](https://www.fastify.io/) as the framework of choice. Fastify is a fast (as the name implies) and low-overhead server in Node. It has grown in popularity, recently crossing the 1 million weekly download mark on npm. I'm a fan of Fastify's plugin architecture, and the ecosystem is quite impressive, boasting over [250 core and community plugins](https://www.fastify.io/ecosystem/).
|
||
|
||
## **Table of Contents**
|
||
|
||
· [Creating the project](#creating-the-project)
|
||
· [Running tests](#running-tests)
|
||
· [Building for production using esbuild](#building-for-production-using-esbuild)
|
||
· [Docker support](#docker-support)
|
||
· [Deploying the server](#deploying-the-server)
|
||
· [Summary](#summary)
|
||
|
||
**Prefer a video version? We've got you covered!**
|
||
|
||
{% youtube src="https://www.youtube.com/watch?v=K4f-fMuAoRY" /%}
|
||
|
||
## Creating the project
|
||
|
||
You can create a new API with a single command.
|
||
|
||
```shell
|
||
$ npx create-nx-workspace@latest \
|
||
--preset=node-standalone \ # create a Node.js project
|
||
--framework=fastify \ # other options are express and koa
|
||
--docker # we'll touch on this later on
|
||
```
|
||
|
||
To run the server in dev-mode, use `npx nx serve` (aliased to `npm start`), and you should see the server starting at port 3000.
|
||
|
||
```
|
||
$ curl <http://localhost:3000>
|
||
{"message":"Hello API"}
|
||
```
|
||
|
||
A couple of notable files:
|
||
|
||
- The `src/main.ts` file is responsible for starting the Fastify server and registering plugins.
|
||
- The `src/app/app.ts` file is the app plugin that provides an initial endpoint at `/` that replies with `{"message": "Hello API"}`.
|
||
|
||
When you edit the source code, the server will reload. You can pass `--no-watch` to disable this behavior.
|
||
|
||
## Running tests
|
||
|
||
In additional to generating the source code, Nx will also create two test suites:
|
||
|
||
1. Unit tests via `npx nx test` (aliased to `npm run test`).
|
||
2. E2E tests via `npx nx e2e e2e` (aliased to `npm run e2e`).
|
||
|
||
Unit tests take advantage of Fastify's plugin architecture, and allows you to test each plugin in isolation. It runs using [Jest](https://jestjs.io/), which is the most popular test runner in Node.
|
||
|
||
```
|
||
// src/app/app.spec.ts
|
||
// This file is generated by Nx.
|
||
import Fastify, { FastifyInstance } from 'fastify';
|
||
import { app } from './app';
|
||
|
||
describe('GET /', () => {
|
||
let server: FastifyInstance; beforeEach(() => {
|
||
server = Fastify();
|
||
server.register(app);
|
||
}); it('should respond with a message', async () => {
|
||
const response = await server.inject({
|
||
method: 'GET',
|
||
url: '/',
|
||
}); expect(response.json()).toEqual({ message: 'Hello API' });
|
||
});
|
||
});
|
||
|
||
```
|
||
|
||
The E2E tests run against the actual server (with all plugins registered), which gives better real-world guarantees.
|
||
|
||
```shell
|
||
$ npx nx serve & # run server in background
|
||
$ npx nx e2e e2e # run test suite
|
||
GET /
|
||
✓ should return a message (27 ms)
|
||
|
||
Test Suites: 1 passed, 1 total
|
||
Tests: 1 passed, 1 total
|
||
Snapshots: 0 total
|
||
Time: 0.429 s
|
||
Ran all test suites.Tearing down... ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— >
|
||
|
||
NX Successfully ran target e2e for project e2e (3s)$ lsof -i:3000 -t | xargs kill # stop server process
|
||
|
||
```
|
||
|
||
There are trade-offs between speed versus confidence when it comes to unit versus E2E tests, which is a topic that is out of scope for this article. I think you should do both, but this is a discussion be held with your team. Nx supports both cases out of the box.
|
||
|
||
## Building for production using esbuild
|
||
|
||
Now that we have our production-ready app, let's examine how Nx handles the build process using [`esbuild`](https://esbuild.github.io/).
|
||
|
||
`esbuild` is a bundler written in Go that is extremely fast — it is much faster than other bundlers like webpack and parcel, but that may change in the future as other tools make their own speed improvements.
|
||
|
||
When you run the build command, Nx uses `esbuild` to generate a self-contained app bundle, which does not require `node_modules`.
|
||
|
||
```shell
|
||
$ npx nx build # aliased to npm build
|
||
> nx run api:build
|
||
|
||
|
||
—————————————————————————————————————————————————————
|
||
|
||
NX Successfully ran target build for project api (2s)
|
||
|
||
```
|
||
|
||
A cool feature of Nx, is that commands such as `build` and `test` are cached if the project (or its dependencies) have not changed. If we run the build a second time, you'll see it completes in a few milliseconds.
|
||
|
||
```shell
|
||
$ npx nx build
|
||
> nx run api:build # existing outputs match the cache, left as is
|
||
|
||
|
||
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— >
|
||
|
||
NX Successfully ran target build for project api (16ms) Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
|
||
|
||
```
|
||
|
||
We'll touch more on the caching when we look at Docker support.
|
||
|
||
And now that the server bundle is ready, we can run it.
|
||
|
||
```
|
||
|
||
$ node dist/api
|
||
{"level":30,"time":1675880059720,"pid":70605,"hostname":"mbp.lan","msg":"Server listening at http://\[::1\]:3000"}
|
||
{"level":30,"time":1675880059721,"pid":70605,"hostname":"mbp.lan","msg":"Server listening at <http://127.0.0.1:3000>"}
|
||
[ ready ] <http://localhost:3000>
|
||
|
||
```
|
||
|
||
You can even run the e2e suite against the production bundle: `npx nx e2e e2e`.
|
||
|
||
## Docker support
|
||
|
||
Recall that we passed the `--docker` option when creating the API project. WIth this option, Nx will generate a default `Dockerfile` and a `docker-build` target.
|
||
|
||
```shell
|
||
# This file is generated by Nx.
|
||
#
|
||
# Build the docker image with `npx nx docker-build api`.
|
||
# Tip: Modify "docker-build" options in project.json to change docker build args.
|
||
#
|
||
# Run the container with `docker run -p 3000:3000 -t api`.
|
||
FROM docker.io/node:lts-alpine
|
||
|
||
ENV HOST=0.0.0.0
|
||
ENV PORT=3000WORKDIR /appRUN addgroup --system api && \
|
||
adduser --system -G api apiCOPY dist/api api
|
||
RUN chown -R api:api .CMD [ "node", "api" ]
|
||
|
||
```
|
||
|
||
To build the image, run `npx nx docker-build`. The image copies only the self-contained bundle, so no `npm install` at all!
|
||
|
||
Nx is smart enough to bundle the app before building the Docker image — because of the `dependsOn` configuration in `project.json`. You can visualize this dependency with `npx nx graph`.
|
||
|
||

|
||
|
||
Now that the image is built, we can run it.
|
||
|
||
```
|
||
|
||
$ docker run -p 3000:3000 -t api
|
||
{"level":30,"time":1675880993256,"pid":1,"hostname":"248744de020a","msg":"Server listening at <http://0.0.0.0:3000>"}
|
||
[ ready ] <http://0.0.0.0:3000>
|
||
|
||
```
|
||
|
||
**Note:** The server binds to `0.0.0.0` so that it can be access from the host machine. You can run curl to verify that it indeed works, or better yet use the E2E test suite (`npx nx e2e e2e`)!
|
||
|
||
## Deploying the server
|
||
|
||
There are numerous platforms that we can deploy our app to. I like [Fly.io](https://fly.io) since it very easy to deploy all over the world using the CLI, and it comes with good [Docker support](https://fly.io/docs/languages-and-frameworks/dockerfile/).
|
||
|
||
If you haven't used Fly before, please follow their [short getting started guide](https://fly.io/docs/speedrun/) (5–10 mins).
|
||
|
||
Once you are ready, let's configure our project.
|
||
|
||
```
|
||
|
||
$ fly launch --generate-name --no-deploy
|
||
|
||
```
|
||
|
||
Follow the prompts and a `fly.toml` file will be generated, which contains the Fly configuration. We need to update this file with the correct port used by our image.
|
||
|
||
```
|
||
|
||
[[services]]
|
||
http_checks = []
|
||
internal_port = 3000 # Make sure this matches what the app listens on
|
||
|
||
```
|
||
|
||
Now we can deploy the app.
|
||
|
||
```
|
||
|
||
$ fly deploy
|
||
|
||
```
|
||
|
||
Fly will log out the monitoring link when the app is successfully deployed.
|
||
|
||

|
||
|
||
And you can open the deployed server using `fly open`.
|
||
|
||

|
||
|
||
That's it! Our server is now deployed for the world to use.
|
||
|
||
## Summary
|
||
|
||
In this post, we saw how easy it is to go from zero code to a deployed server using Nx. Here is a quick summary of the points.
|
||
|
||
1. Use `create-nx-workspace --preset=node-standalone --framework=fastify --docker` to quickly create a Fastify server project.
|
||
2. Nx provide both unit test and E2E test suites — `npx nx test` and `npx nx e2e e2e`.
|
||
3. Nx builds the server using esbuild — `npx nx build`.
|
||
4. Docker support is provided out of the box via the `--docker` option, and Nx understands that Docker build depends on the app to be bundled. Run it via `npx nx docker-build`.
|
||
5. Deploying to Fly (or other platforms) is easy since we have a Docker image.
|
||
|
||
To learn more about Nx and what else it can do, refer to the [intro page](/getting-started/intro) in the docs.
|
||
|
||
## Learn more
|
||
|
||
- 🧠 [Nx Docs](/getting-started/intro)
|
||
- 👩💻 [Nx GitHub](https://github.com/nrwl/nx)
|
||
- 💬 [Nx Official Discord Server](https://go.nx.dev/community)
|
||
- 📹 [Nrwl Youtube Channel](https://www.youtube.com/@nxdevtools)
|
||
- 🥚 [Free Egghead course](https://egghead.io/courses/scale-react-development-with-nx-4038)
|
||
- 🚀 [Speed up your CI](/nx-cloud)
|