NestJS has been my go-to Back-End framework for API development in NodeJS for quite a while now after discovering how annoying it can be to set up a NodeJS + Express application, not to mention that if you want TypeScript support, it requires quite a lot of setting up to do.
Also, I believe implementing Docker for development (and definitely for production) is a must have thing in all of my Back-End applications since it makes deployment extremely easy and predictable no matter which service it's going to be deployed to.
Prerequisites
I'm not going to go too much in depth on NestJS or Docker since the main focus of this tutorial is to run a NestJS app environment with Docker and that's about it. I will be making a separate series about NestJS and Docker going more in depth along with best practices in the future.
In order to fully understand how the upcoming code works, you should have a basic understanding of the following subjects:
- Working with Terminal
- JavaScript/TypeScript
- How Docker works
Let's begin!
Creating a new NestJS application
Start by installing the NestJS CLI using npm
on your machine and create a new project:
$ npm i -g @nestjs/cli
$ nest new nestjs-docker
When creating a new project, NestJS is going to ask you
Which package manager would you ❤️ to use?
I am going to choose npm
for this tutorial, but you can choose yarn
if you really want to.
After the installation is finished, cd
into your new application directory and run the app like so:
$ cd nestjs-docker
$ npm run start:dev
Then, open your newly created app in the browser using the address http://localhost:3000 in order to make sure everything runs smoothly.
Creating a new API service
Let's create a new API service that returns a hardcoded array of movies that we can use to test our API after containerizing it with Docker.
Open the newly created project directory with your favorite text/code editor (I'm using Visual Studio Code).
Your workspace should look like something like this:
Then, open the src
directory. Here the source files of the application can be found in order to start our development
Open the file app.service.ts
and replace the whole content of the file with the following code:
import { Injectable } from '@nestjs/common';
export interface Movie {
id: number;
name: string;
year: number;
}
@Injectable()
export class AppService {
private movies: Movie[] = [
{ id: 1, name: 'Star Wars: The Force Awakens', year: 2015 },
{ id: 2, name: 'Star Wars: The Last Jedi', year: 2017 },
{ id: 3, name: 'Star Wars: The Rise of Skywalker', year: 2019 },
];
getMovies(): Movie[] {
return this.movies;
}
}
NOTE: Adding an exportable Model (e.g. Movie
) to the service class file is definitely not a good practice and you shouldn't do this under any circumstance. It's for demo purposes only. Use a different model file for your apps.
Next, open the file app.controller.ts
and replace the whole content of the file with the following code:
import { Controller, Get } from '@nestjs/common';
import { AppService, Movie } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getMovies(): Movie[] {
return this.appService.getMovies();
}
}
Run the project again using npm run start:dev
and open the app in the browser at http://localhost:3000 or you can use Postman and create a new GET
request for a more formatted an semantic workflow.
The final result should look like this:
Let's Dockerize this!
Now that we have our Back-End API app up and running, let's containerize it using Docker for development.
Start by creating the following files in the project's root directory:
-
Dockerfile
- This file will be responsible for importing the Docker images, divide them intodevelopment
andproduction
environments, copying all of our files and installnpm
dependencies -
docker-compose.yml
- This file will be responsible for defining our containers, required images for the app other services, storage volumes, environment variables, etc...
Open the Dockerfile
and add the following code inside:
FROM node:12.19.0-alpine3.9 AS development
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install glob rimraf
RUN npm install --only=development
COPY . .
RUN npm run build
FROM node:12.19.0-alpine3.9 as production
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . .
COPY --from=development /usr/src/app/dist ./dist
CMD ["node", "dist/main"]
Open the docker-compose.yml
file and add the following code:
version: '3.8'
services:
dev:
container_name: nestjs_api_dev
image: nestjs-api-dev:1.0.0
build:
context: .
target: development
dockerfile: ./Dockerfile
command: npm run start:debug
ports:
- 3000:3000
- 9229:9229
networks:
- nesjs-network
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
restart: unless-stopped
prod:
container_name: nestjs_api_prod
image: nestjs-api-prod:1.0.0
build:
context: .
target: production
dockerfile: ./Dockerfile
command: npm run start:prod
ports:
- 3000:3000
- 9229:9229
networks:
- nesjs-network
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
restart: unless-stopped
networks:
nesjs-network:
Running the Docker containers
Now that we have defined our Docker files, we can run our app solely on Docker.
To start our app, write the following command in your terminal:
docker-compose up dev
This will start it in development mode. We even get a file watcher when saving our files so we don't have to re-run it every time we make change 😍.
And to start our app in production mode, you guessed it... run the following command in your terminal:
docker-compose up prod
Make a GET
request yet again to http://localhost:3000 and... voilà! Should work as expected.
P.S: If you want to ditch the terminal logging, you can run the container in a separate daemon using the -d
flag like so:
docker-compose up -d prod
Done!
You can find the full source code right here.
Feel free to let me know what you think in the comments and ask away any questions you might have ✌️
Top comments (16)
Nicely done!
We created an open-source project that let you create a full functional NestJS backend with GraphQL and REAT API over your custom model in minutes. We generate a high quality TypeScript code and let you continue development on your own.
You are invited to try it on
app.amplication.com
The repo is in github.com/amplication/amplication
Looks promising! Will look into it 🙂
Nice post!
But production stage better not have
COPY . .
Got my project dockerized, thank you!
Nice work! Got it working first try thanks to the clear instructions!
Thank you!!! Nice article!! Works perfectly!
I have a doubt. If I only run "docker-compose up prod" I dont have a '/dist' folder by then so why would you use "COPY --from=development /app/dist ./dist" ? Why dont we have a RUN npm build in the production target? If these are like 2 different images, why are depending on development dist folder, or the whole process takes place when built? Im somehow confused there
Hi, nice article. One important note: in the production stage of Dockerfile there should not be a
COPY . .
command. On prod we just need to start the app from thedist
folder and having all sources in the image is at least not necessary.One a day I shown NestJS to Angular team and they built MVP and production ready beta in a couple weeks like they always knew how to cook a great API.
GraphQL, tests and docs out of the box. Great tool 👍🏼
NestJS is golden for Angular developers who want/need to go Full-Stack. The syntax and workflow is so similar, which makes it seamless to move between the two. Great choice and recommendation 👍👍👍
Hi Erez Hod. Nice article. Could you please explain how better works with node_modules in your configuration? I mean we have no node_modules with this configuration. But we need it for vscode for (development process linter, prettier, etc), and also what I need to do if I need to install an additional npm package?
Do we need to install node_modules also through npm install (by locally) for vscode (development process linter, prettier, etc)? And for installation of additional node_module, we need to stop docker-compose, install additional node_module and after that run docker like docker-compose up --build -V for rebuilding node_modules in our volume. Is it correct?
nice read. How to add a postgres db to start along with nestjs app out of the box