Trying out NestJS part 1: Setting up dev environment for your React / NestJS applications that rocks

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 with sudo)
  • 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.

Part 2 is available here.

Arnaud Cortisse © 2024