Introduction
In the context of my current job, I wanted to evaluate the various existing backend frameworks based on NodeJS. Why is that? The only Node.js backend framework I had ever used so far was express, which is an awesome light-weight framework, but that doesn't have any opinion whatsover on how you should structure your app.
During my investigation, I came across NestJS several times. The most appealing thing to me was its thorough documentation and its large ecosystem. I was especially interested in the OpenAPI integration, which I knew could greatly improve the frontend development experience when coupled with a code generator. In the end, I decided to create a small POC to see whether it would be a fit.
Specifications of the project
Functional requirements
The POC is going to be a minimal, hideous "TODO list" app (styling is not in the scope of this endeavor). In this POC, I'll be able to:
- Add tasks,
- Remove tasks,
- List all tasks
Technical requirements
- Use Typescript everywhere.
- NestJS for the backend.
- React for the frontend.
- Tasks are saved in a Postgres DB.
- Redis is used for caching responses.
- API endpoints are documented using OpenAPI.
- API endpoints' parameters are validated in the backend.
- Frontend code related to the API endpoints is auto-generated.
- The development environment is set up in docker.
- Monorepo containing both the backend and the frontend.
Building the project
The source code for this part of the project is available here: https://github.com/arnaud-cortisse/trying-out-nestjs-part-1.
Setting up Docker dev environment
Since I took Docker and Kubernetes: The Complete Guide and Microservices with Node JS and React courses, I've been a huge fan of setting up my dev environment inside docker instances instead of setting it up directly on my machine. I love the fact that I can have everything up and running with a single command, without having to worry about dependency conflicts (is my current version of NPM compatible with that project?, etc.).
A few commands to execute
- Install the Nest CLI:
npm i -g @nestjs/cli
(you might need to prefix it withsudo
) - Create an empty folder:
mkdir todo-list-app
- Go inside the folder:
cd todo-list-app
- Init npm package:
npm init -y
- Init git, if you want to save your work:
git init
- Create frontend folder:
mkdir -p packages/react-app
- Create backend folder:
mkdir -p packages/nestjs
- Create the React app:
npx create-react-app packages/react-app --template typescript
- Create the NestJS app:
nest new packages/nestjs
- Delete the .git folder automatically created by NestJS:
rm -rf packages/nestjs/.git
- Create frontend env variables file:
touch packages/react-app/.env.dev
- Create backend env variables file:
touch packages/nestjs/.env.dev
- Create frontend Dockerfile for dev environment:
touch packages/react-app/Dockerfile.dev
- Create backend Dockerfile for dev environment:
touch packages/nestjs/Dockerfile.dev
- Create docker-compose file for dev environment:
touch docker-compose.yml
- Create frontend .dockerignore file:
touch packages/react-app/.dockerignore
- Create backend .dockerignore file:
touch packages/nestjs/.dockerignore
A few files to fill / change
packages/react-app/Dockerfile.dev
FROM node:alpine
WORKDIR /app
COPY package.json .
RUN npm install --legacy-peer-deps
COPY . .
CMD ["npm", "run", "start"]
--legacy-peer-deps
is just a temporary fix for https://github.com/facebook/create-react-app/issues/9515.
packages/nestjs/Dockerfile.dev
FROM node:alpine
WORKDIR /app
RUN npm install -g @nestjs/cli
COPY package.json .
RUN npm install
COPY . .
CMD ["npm", "run", "start:dev"]
Nothing crazy here, but we just make sure we install the NestJS CLI globally before doing anything else.
packages/react-app/.env.dev
REACT_APP_BACKEND_SCHEMA=http
REACT_APP_BACKEND_HOSTNAME=localhost
REACT_APP_BACKEND_PORT=3001
CHOKIDAR_USEPOLLING=true
CHOKIDAR_USEPOLLING
is required when developing inside a docker container and using create-react-app (https://github.com/facebook/create-react-app/issues/1049#issuecomment-261731734).
The other variables are defined so that we can communicate with the NestJS API.
packages/nestjs/.env.dev
NEST_PORT=3001
PGHOST=postgres
PGPORT=5432
PGUSER=postgres
PGPASSWORD=example
PGDATABASE=postgres
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=password
REDIS_TTL=10
We define the port on which NestJS will run as well as the Postgres and Redis configs.
packages/react-app/.dockerignore
node_modules
We don't want the local node_modules
folder to be copied over the instance.
packages/nestjs/.dockerignore
node_modules
docker-compose.yml
version: "3.5"
services:
nestjs:
build:
context: ./packages/nestjs
dockerfile: Dockerfile.dev
env_file:
- ./packages/nestjs/.env.dev
ports:
- 3001:3001
volumes:
- ./packages/nestjs/:/app
- /app/node_modules
react_app:
build:
context: ./packages/react-app
dockerfile: Dockerfile.dev
env_file:
- ./packages/react-app/.env.dev
ports:
- 3000:3000
volumes:
- ./packages/react-app/:/app
- /app/node_modules
postgres:
image: postgres:13.1
environment:
POSTGRES_PASSWORD: example
ports:
- 5432:5432
redis:
image: redis:6.2-rc1
environment:
REDIS_PASSWORD: password
redis_commander:
image: rediscommander/redis-commander:latest
restart: always
environment:
- REDIS_HOSTS=local:redis:6379
ports:
- 8081:8081
depends_on:
- redis
nestjs
is our backend.react_app
is our frontend.postgres
is going to be used to store the tasks.redis
is going to be used by NestJS to cache responses.redis_commander
is just a tool that allows us to examine the Redis DB quickly.volumes
under react_app and nestjs is key to get auto-reload whenever you modify files inside your editor. The only annoying thing with this setup is that you'll need to rebuild your docker images whenever you add a new dependency inside your node_modules (see https://github.com/BretFisher/node-docker-good-defaults for fixes).
packages/nestjs/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.NEST_PORT);
}
bootstrap();
Modify the port the app is listening to using the process.env.NEST_POST environment variable (defined in packages/nestjs/.env.dev).
Test current setup
You should now be able to start your dev environment typing docker-compose up
in the root directory.
You can then go to the following addresses:
localhost:3000
--> React app.localhost:3001
--> NestJS app.localhost:8081
--> Redis Commander (which should be connected to your Redis instance).
Final words
With the current state, I've a working dev environment inside dev containers. All I have to do to get started is docker-compose up
(sometimes, I have to do a docker-compose up --build
, depending on whether or not new npm packages have been installed).
Whenever I update any .ts
files in my code editor, the apps are reloaded accordingly, making it a very pleasant dev experience for the task at hand: asserting whether or not NestJS is going to be a good fit for me by developing a POC.