DEV Community

Cover image for Setting up a NestJS project with Docker for Back-End development
Erez Hod
Erez Hod

Posted on

Setting up a NestJS project with Docker for Back-End development

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:
Alt Text

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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:
Alt Text

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 into development and production environments, copying all of our files and install npm 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"]
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ✌️

Discussion (9)

Collapse
yuvalhazaz profile image
Yuval Hazaz

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

Collapse
erezhod profile image
Erez Hod Author

Looks promising! Will look into it 🙂

Collapse
truezombie profile image
Oleg Ovcharenko • Edited

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?

Collapse
dmkv profile image
Dmitry Makeev

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 👍🏼

Collapse
erezhod profile image
Erez Hod Author

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 👍👍👍

Collapse
manhhailua profile image
Mạnh Phạm

Nice post!
But production stage better not have COPY . .

Collapse
codefalcon95 profile image
codefalcon95

Hello, i tried this example and the prod version didnt start up always giving Error: Cannot find module '/usr/src/app/dist/main'.

Collapse
erezhod profile image
Erez Hod Author

Hi 🙂
How is your docker-compose.yml defined? also, does your app run using npm run start:prod without Docker?

Collapse
dewaldels profile image
Dewald Els

Nice work! Got it working first try thanks to the clear instructions!