Introduction
I sometimes find myself trying to remember exactly how to initiate a project after a very long time away. At this point, I've probably spent the last couple months working on a project off an entirely different stack, so much that I haven't initiated a new project in a while.
I decided to create this go-to article for initiating a new project using nestjs with typescript. I also thought it'll be a handy tool for other developers to leverage when getting their backend projects started. I'll simply give a step by step process to get a project started, create it's basic structure, connect to a database using docker and implement a authentication service.
Creating a NestJs App
First, I'll be going through creating a nestjs application, connecting to a postgresql
database that will be spun up using docker
, creating a User model and configuring an ORM
- in this case prisma
- to connect with our database. I'll also try to implement a simple authentication system to add some functionality to the app.
To get started with nestJs
"a framework for building efficient, scalable Node.js server-side applications", I'll go to my terminal and install nest cli
globally using the command below. This provides a way to create a new nest project which bundles with it the core files required to get the project started.
% npm i -g @nestjs/cli
I'll start the new project with this command -nest new <project-name>
- which creates a directory where all our files will reside. It also creates a basic structure for the whole application.
% nest new nest-init
... # select a preferred package manager
% code nest-init # or open in your favorite IDE
Now, the working directory looks something like
|-- node_modules/
|-- src
| |-- main.ts
| |-- app.module.ts
|-- test/
|-- package.json
|-- README.md
|-- tsconfig.json
NOTE: The src
directory initially contained more files but I removed them because I won't be needing them.
The Database
Starting the database
The next thing to do is to set up the database for the app to connect to. I'll do this using docker-compose
which is a tool for "running multi-container applications on docker". I start by creating a docker-compose.yml
file which will contain the code for running a postgreSQL instance. The file should contain something similar to the code below
version: '3.8'
services:
postgres:
image: postgres:13-alpine
ports:
- 5432:5432
env_file:
- .env
volumes:
- postgres:/var/lib/postgresql/data
networks:
- nest-init
volumes:
postgres:
name: nest-init-docker-db
networks:
nest-init:
To make use of this, a .env
file is required which contains the credentials to be used when spinning up the postgreSQL instance. It should contain the following constants
POSTGRES_DB=<db_name>
POSTGRES_USER=<username>
POSTGRES_PASSWORD=<password>
I then proceed to run the command below which spins up the database with docker; the -d
flag allows this to run in detached mode which in-turn makes the terminal session still available for use.
% docker-compose up -d
Connecting to the database
Now that the database is running, I'll proceed to install and initiate prisma
which creates a prisma directory and schema file. It also creates a .env
file or in this case, adds a required line; DATABASE_URL
to the file.
The database url in this case will look something like postgresql://dami:root@localhost:5432/nest-init?schema=public
.
To install prisma
% npm install prisma --save-dev
...
% npx prisma init
Now I have a schema.prisma
file that contains the database connection variable. It will also contain all the yet to be created models that will be used in the application. It also defines the client
to be used which in this case is prisma client
. To make use of it though, it needs to be installed by running the command below
% npm install @prisma/client
I also need to install nest config
to easily access our configuration variables in the .env
file.
% npm i --save @nestjs/config
The ConfigModule
needs to be added to the app.module.ts
file to be able to make use of the config service with the aid of dependency injection
. Now my file looks like this
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true
}),
PrismaModule
],
})
export class AppModule {}
I'll also create a simple User model to be used for the authentication system.
model User {
id Int @id @default(autoincrement())
email String @unique
name String
password String
@@map("users")
}
After creating the model, a migration needs to be made so the model will be mapped to the database. This is done by running a simple prisma command
% npx prisma migrate dev
This creates a SQL migrations file and runs the migrations on the database.
Creating required services
Now, I can start creating the required services. Nest CLI
provides handy tools for creation of modules, services and controllers. It also automatically bundles related files in a directory and adds the individual service module to the app.module.ts
file. At the end or a project, the src
folder should look something like;
src
|-- auth
| |-- auth.controller.ts
| |-- auth.module.ts
| |-- auth.service.ts
|-- prisma
| |-- prisma.module.ts
| |-- prisma.service.ts
|-- app.module.ts
|-- main.ts
Prisma Service
I'll start by creating the prisma service while would be used to access the database from any part of the application.I'll generate to files, the prisma.module.ts
and prisma.service.ts
files using the command below.
% nest g module prisma
% nest g service prisma --no-spec
The --no-spec
flag ensures that prisma doesn't generate test files alongside the required files. Next, I'll add the following code to the prisma.service.ts
file
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient{
constructor(config: ConfigService) {
super({
datasources: {
db: {
url: config.get('DATABASE_URL')
}
}
})
}
}
and the prisma.module.ts
file looks like
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService]
})
export class PrismaModule {}
With this, I can also inject the PrismaService
wherever the application needs to access the database.
Auth Service
Going forward, I'll be implementing a simple register
endpoint in the authentication system to to test the apps functionality. To do this, the auth service
, module
and controller
files need to be generated using the commands below
% nest g module auth
...
% nest g service auth --no-spec
...
% nest g controller auth --no-spec
The generated module file already contains imports and bundles the service and controller files. I'll go ahead to add the route to the controller file and the register
logic in the service file.
I'll create a simple DTO to serve as a schema for the expected payload and use class-validator
to validate the incoming data. Note that this needs to be installed running
% npm i class-validator
% npm i class-transformer
The validation pipe also needs to be added to the main.ts
file to allow validation in the whole application.
main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true
}))
await app.listen(3000);
}
bootstrap();
Now I'll write the auth controller
auth.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { IsEmail, IsNotEmpty, IsString } from "class-validator"
class RegisterDTO {
@IsNotEmpty()
@IsEmail()
email: string
@IsNotEmpty()
@IsString()
name: string
@IsNotEmpty()
@IsString()
password: string
@IsNotEmpty()
@IsString()
repeatPassword: string
}
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('register')
register(@Body() dto: RegisterDTO) {
return this.authService.register(dto)
}
}
And in the auth.service.ts
file
import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
import { PrismaService } from 'src/prisma/prisma.service';
import { RegisterDTO } from './auth.controller';
import * as CryptoJS from 'crypto-js';
@Injectable()
export class AuthService {
constructor(private prisma: PrismaService, private config: ConfigService) {}
async register(dto: RegisterDTO) {
try {
if (dto.password !== dto.repeatPassword) throw new BadRequestException("Passwords must be equal!")
const key = CryptoJS.enc.Utf8.parse(this.config.get('SECRET_PASS'));
const passwordHash = CryptoJS.AES.encrypt(dto.password, key, {iv: key}).toString()
const user = await this.prisma.user.create({
data: {
name: dto.name,
email: dto.email,
password: passwordHash
}
})
const {password, ...result} = user
return {
statusCode: 201,
message: "User created successfully.",
data: result
}
} catch (error) {
console.log(error)
if (error instanceof PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
throw new ForbiddenException("Email or name has already been used!")
}
} throw error;
}
}
}
Now I can start the server by running npm run start:dev
and make a request to the register endpoint.
The complete code can be found here on Github.
Cheers!
Top comments (1)
Very useful. Thank you