NestJs and Docker with some AWS (Part 1)
Overview
I just thought I would put a lot of this into a medium article as it’s something I’m playing about with a bit over Christmas and hoped it might help others doing similar.
The end goal is to have an API that works in a docker container and can be deployed to an AWS instance. We will build it up over the next few weeks. In this instalment, we will simply get a local instance of a NestJs server up and running. I’ll add additional articles as I get them done.
Pre-Requisites
This is a slightly more advanced article but should still be achievable by someone with a decent understanding of development processes. I’m going to assume you are comfortable with TypeScript, the console, and making HTTP requests. You will need npm, docker and an Ide such as VsCode or WebStorm. I’m using windows and have never strayed down the Linux/Mac path so please be aware this might not go well on them platforms but I think most of it should still be straightforward enough
It all beings with visiting the NestJs site
npm i -g @nestjs/cli
nest new nest-api-docker
cd into the folder or open the folder with Vscode or Webstorm. My preference is WebStorm it’s not free but it just seems to make me much more productive than VsCode for some reason. I’ve tried to find plugins to match etc. I can’t seem to make VScode become what Webstorm is for me.
From here I would run
npm run start:dev
To check that everything is up and running. If successful you should get output similar to this:
The first thing I want to add is an end-point to show info about the server.
npm i nestjs-real-ip
Then update the app.controller.ts
file to look like this
import { Controller, Get } from '@nestjs/common';
import { RealIP } from 'nestjs-real-ip';
@Controller()
export class AppController {
@Get('')
getInfo(@RealIP() ip: string) {
return {
version: process.env.npm_package_version,
env: process.env.NODE_ENV,
ip,
date: new Date().toISOString(),
};
}
}
This will tell us what version of the server is based on the package.json file, the env it is running under, and the requesting user's IP.
We will also tweak the main.ts
file to allow us some flexibility with the port. I’ve defaulted to 6000 as it stops a lot of conflict with the 3000 that everything, like to, use
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
const port = process.env.PORT || 6000;
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(port);
}
bootstrap()
.catch((error) => console.error(error))
.then(() => console.log(`Listening on http://localhost:${port}`));
You should now see output like that below when you run the dev command
You can then use postman or any similar HTTP request plugins to hit the endpoint
We are now going to Dockerize this bad boy. Start by creating a Dockerfile
and a .dockerignore
in the root of the project. Most of this is stolen from the Tom Ray article in the references. There is much better information about why this is all set up the way it is.
Add the following to the DockerFile
###################
# BUILD FOR LOCAL DEVELOPMENT
###################
FROM node:18-alpine As development
# Create app directory
WORKDIR /usr/src/app
# Copy application dependency manifests to the container image.
# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
# Copying this first prevents re-running npm install on every code change.
COPY --chown=node:node package*.json ./
# Install app dependencies using the `npm ci` command instead of `npm install`
RUN npm ci
# Bundle app source
COPY --chown=node:node . .
# Use the node user from the image (instead of the root user)
USER node
###################
# BUILD FOR PRODUCTION
###################
FROM node:18-alpine As build
WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./
# In order to run `npm run build` we need access to the Nest CLI which is a dev dependency. In the previous development stage we ran `npm ci` which installed all dependencies, so we can copy over the node_modules directory from the development image
COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules
COPY --chown=node:node . .
# Run the build command which creates the production bundle
RUN npm run build
# Set NODE_ENV environment variable
ENV NODE_ENV production
# Running `npm ci` removes the existing node_modules directory and passing in --only=production ensures that only the production dependencies are installed. This ensures that the node_modules directory is as optimized as possible
RUN npm ci --only=production && npm cache clean --force
USER node
###################
# PRODUCTION
###################
FROM node:18-alpine As production
# Copy the bundled code from the build stage to the production image
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
COPY --chown=node:node --from=build /usr/src/app/dist ./dist
# Start the server using the production build
CMD [ "node", "dist/main.js" ]
in the .dockerignore
file add
Dockerfile
.dockerignore
node_modules
npm-debug.log
dist
Run this command from the terminal. This will take a few minutes with a lot of console output
docker build -t nest-api-docker .
# followed by
docker run -p 6000:6000 nest-api-docker
Next, we will add docker-compose.yml
file with the following contents
version: '3'
services:
nest-api-docker:
environment:
- NODE_ENV=development
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/usr/app
container_name: nest-api-docker
expose:
- "6000"
ports:
- "6000:6000"
We can now run
docker compose up --build
This should spin us up a working container. NOTE: make sure you set the sharing permission on the folder when asked or you might get some errors.
I would also recommend the desktop version of Docker for people who like GUI’s more than commands. You get some cool output and lots of controls
GIT
We will just add a few things that will help with git and overall controlling commits etc. Run the next command from a terminal and add these packages
npm i -D @commitlint/config-conventional commitizen commitlint conventional-github-releaser
Add the following item to the package.json
file after the jest one and just before the final funky brace
...JEST,
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
This will allow us to use commitizen style conventional commits which not only keep the commits consistent but allow for automation of release notes etc. later on. Run the following commands to stage all the changes and then follow the commit prompts
git add .
git-cz
From here I created a Github repo so we have somewhere to keep the code for now.
Follow the last 2 lines as the rest should already be in place
git remote add origin git@github.com:AndrewAllison/nest-api-docker.git
git push -u origin main
You should now have access to the public repo at https://github.com/AndrewAllison/nest-api-docker
Conclusion
Ok so this part was relatively short and sweet we will move on to the next part which should be coming in the following days. Take care and See you soon.