Nx and Node Microservices
In this post, we’ll go over how we can use Nx to create microservices using Node and Docker.
We’ll be creating a simple product that will have two applications (a view application, and an API application).
And we will have a Docker container running on http://localhost:8080
that will have two services communicating with each other within the container.
Prerequisites
Since we’re going to be creating images with Docker, it will need to be installed first. You can follow Docker’s install guide at https://docs.docker.com/desktop
Getting Started
Let’s start by creating a new Nx workspace, run either one of the following:
npx create-nx-workspace --preset=nest
or
yarn create nx-workspace --preset=nest
Then set the following options:
Workspace name (e.g., org name) my-org
Application name api
Next, we’ll add another application. Change directories into the newly created workspace (cd my-org
) and run the following command:
npm run nx generate application html
or
yarn nx generate application html
After the html
project is created, we’ll add a Node library to share some interfaces between the html
and api
projects.
Run the following commands to install @nrwl/node
and create a library project:
npm install -D @nrwl/node
npm run nx generate @nrwl/node:library todos
or
yarn add -D @nrwl/node
yarn nx generate @nrwl/node:library todos
For future examples, I’ll omit
npm
oryarn
, to keep the commands short. You can continue using whichever tool you’re comfortable with.
After everything is set up, we should have the following project structure:
.
├── apps/
│ ├── api/
│ │ ├── src
│ │ │ ├── // source files
│ │ ├── jest.config.js
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.json
│ │ └── tsconfig.spec.json
│ └── html/
│ │ ├── src
│ │ │ ├── // source files
│ │ ├── jest.config.js
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.json
│ │ └── tsconfig.spec.json
├── libs/
│ └── todos/
├── tools/
├── jest.config.js
├── jest.preset.js
├── nx.json
├── package.json
├── tsconfig.base.json
└── workspace.json
Now that we have Nx workspace set up we can begin editing some code
Let’s start with editing libs/todos/src/lib/todos.ts
and just export an interface:
Then edit both main files for app and html (apps/api/src/main.ts
and apps/html/src/main.ts
) and remove the globalPrefix
variables. We won’t be needing this because we want this application to just run directly on the host without having to go into deep URLs.
API service
Now that some setup is done, we can edit the apps/api/src/app/app.service.ts
file to send some todos.
Notice that we can import directly from the todos library without having to go deep into relative paths. This is one of the abilities that is provided by a Nx workspace automatically when creating libraries. 👍
Run the application with the following command:
nx serve api
And verify that data is returned when we go to the endpoint via the browser at http://localhost:3333
. We should see some JSON in the browser, with some expected todos. 🎉
HTML service
We should now have our API service working as expected. Now we need to change the html
project to call the api
and render a UI.
We’ll need to install some dependencies:
yarn add axios hbs
Axios will be used to do some HTTP calls from Node easily.
Hbs is a template engine used to convert Handlebars templates to plain html.
Now let’s set up our html
project to render some Handlebars templates.
Create the following file under the apps/html/src/assets/views/index.hbs
path.
Then let’s modify apps/html/src/main.ts
to enable server side templates:
Something that’s important to note is that we also updated the port to start on
3334
. We need this so that we can start both theapi
application andhtml
application together
Next up, modify apps/html/src/app/app.controller.ts
so that the getData
retrieves data from the api
application. We’ll also need to add an index
renderer.
This application imports the same
Todos
interface from@my-org/todos
. This will keep thehtml
and theapi
application in sync.Also to note: The Nest framework provides a microservices package that would be better suited for production. You can read more about that on the Nest documentation site.
Now the html
application is ready to get data from the running api
application.
Running multiple applications
To test locally, we will need to make sure that both projects are running. Thankfully, Nx provides a mechanism to run multiple projects together.
If you still have your
api
application running, stop that process.
In the terminal, run the following command:
nx run-many --target=serve --projects=api,html --parallel
This is the equivalent to running nx serve <project>
in multiple terminals.
If you open the http://localhost:3334
link in your browser we should see a UI that retrieved data from another service
Building Docker images
Let’s build our applications to get them ready to be put into Docker images.
When creating Docker images for Node applications, we need to make sure that there’s a package.json available. We need the package.json to install application dependencies in the image.
Nx has mechanisms to automatically create a package.json based on dependencies an application is using. To enable this, we need to modify the workspace.json
file and enable package.json generation.
Add the generatePackageJson
property to each of the build targets:
Now we can run the following command to build the applications:
nx run-many --target=build --projects=api,html --parallel
Now we should have a couple folders in the dist/
directory. We can also see the generated package.json files as well.
If we compare the two package.json files we can see that the
html
project contains more dependencies. This is because we’re using these explicitly in thehtml
application.
Next we’re going to create a couple Dockerfiles.
Create the following in the apps/api/Dockerfile
path:
FROM node:lts-alpine
WORKDIR /app
COPY ./dist/apps/api .
ENV PORT=3333
EXPOSE ${PORT}
RUN npm install --production
# dependencies that nestjs needs
RUN npm install reflect-metadata tslib rxjs @nestjs/platform-express
CMD node ./main.js
And one in the apps/html/Dockerfile
:
FROM node:lts-alpine
WORKDIR /app
COPY ./dist/apps/html .
ENV PORT=3334
EXPOSE ${PORT}
RUN npm install --production
RUN npm install reflect-metadata tslib rxjs hbs
CMD node ./main.js
There’s an additional
RUN npm install
command in each of the Docker files. These are the dependencies that NestJs needs that aren’t explicitly used in our applications.
We can confirm that the docker images build by running the following commands in the root of the workspace:
docker build -f ./apps/api/Dockerfile . -t api
docker build -f ./apps/html/Dockerfile . -t html
Deploy command
Having to remember all these commands can be confusing. Thankfully, Nx has another mechanism where we can combine multiple commands together. This is provided by the @nrwl/workspace:run-command
executor.
Let’s modify the workspace.json
file and add a new target: deploy
.
Now if we want to always build fresh images, we can run nx deploy api
or nx deploy html
and Nx will run the build command then call docker to build the image.
Composing
There are a couple more things we need to do to make sure that our apps are ready to be run in a container with each other.
Create a docker-compose.yml
file in the root of the workspace with the following:
version: '3.8'
services:
html:
image: html
environment:
- apiPath=http://api:3333
ports:
- '8080:3334'
api:
image: api
ports:
- '8081:3333'
In the case of Docker Compose, images can communicate with each other by their names in services. So for the html
application to communicate with api
, we have to make sure that the URL is set up correctly. Calling localhost:3333
from the html
container will not work.
So we included the environment key with an apiPath
variable that would map to the api
container’s address.
Now we need to modify the html
application to know about this new environment variable.
Edit the apps/html/src/app/app.controller.ts
file with the following:
Since we modified the html
application, let’s rebuild everything for it with nx deploy html
.
We can also run
nx affected --target=deploy
and use Nx’s affected algorithm to automatically build only projects that were changed
When everything is built and deployed, run the following command:
docker-compose up
Once everything is finished setting up, open localhost:8080
in your browser. You should now see the html
application running with our todos! 🎉
Summary
In the end, this is what we did:
- Created a new workspace
- Created a library, and applications
- Set up Server side rendering for one application
- Created Dockerfiles
- Added new targets in our
workspace.json
- Built our applications and deployed Docker images
- Ran our images in a container with
docker-compose
docker-compose
isn’t the only tool we could have used to orchestrate containers. As a next step, diving into Kubernetes could be fun and interesting!
Hopefully this guide will help you get started with how you can implement Docker with your own Nx workspace.
You can see the final code at github.com/nrwl/node-microservices.