Introduction
In the connected world of today, developing scalable and dependable web APIs is crucial for any modern application. Using PostgreSQL for the database, Sequelize as the Object-Relational Mapper (ORM), and NestJS for the backend framework, this blog post will walk you through creating a robust API. You will understand how to create your development environment, connect to a PostgreSQL database, define models, and carry out CRUD (Create, Read, Update, Delete) operations by the end of this tutorial.
Why This Stack?
- NestJS:Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript and is built with full support for TypeScript.
- PostgreSQL: PostgreSQL is a robust, open-source object-relational database system that is renowned for its performance, feature robustness, and dependability.
- Sequelize: Sequelize is a popular Object Relational Mapper (ORM) written in vanilla JavaScript, but there is a sequelize-typescript TypeScript wrapper that provides a set of decorators and other extras for the base Sequelize.
Requirements
- Node.js (LTS version suggested)
- npm or Yarn PostgreSQL
- Basic familiarity with REST APIs and TypeScript
- First Step: Launch Your NestJS Project
- Let's start by making a new NestJS project. Install the NestJS CLI globally first if you haven't already:
Step 1: Set Up Your NestJS Project
Install the NestJS CLI globally first if you haven't already:
npm i -g @nestjs/cli
Create your new project:
nest new nestjs-api-boilerplate
cd nestjs-api-boilerplate
This will set up a basic NestJS application.
Step 2: Install Database Dependencies
Next, we need to install the necessary packages for PostgreSQL and Sequelize:
npm install --save @nestjs/sequelize sequelize pg pg-hstore
# pg-hstore is for serializing/deserializing JSON data
pg is the PostgreSQL driver, and sequelize-typescript enables TypeScript decorators in Sequelize models.
Step 3: Configure PostgreSQL
Make sure your PostgreSQL server is running. Create a new database for your application. For example, let's call it nestjs_api_db.
Step 4: Integrate Sequelize with NestJS
NestJS provides an @nestjs/sequelize package to easily integrate Sequelize.
- Create a Database Configuration Module: Let's create a
database.module.tsto handle our database connection.// src/database/database.module.ts import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; import { ConfigModule, ConfigService } from '@nestjs/config'; // For environment variables @Module({ imports: [ SequelizeModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ dialect: 'postgres', host: configService.get('DB_HOST'), port: configService.get('DB_PORT'), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), autoLoadModels: true, // Automatically load models synchronize: true, // Auto-create tables (for development, disable in production) }), inject: [ConfigService], }), ], }) export class DatabaseModule {} - Add Environment Variables: Create a
.envfile in your project root to store your database credentials.
Important: Replace# .env DB_HOST=localhost DB_PORT=5432 DB_USERNAME=your_pg_username DB_PASSWORD=your_pg_password DB_DATABASE=nestjs_api_dbyour_pg_usernameandyour_pg_passwordwith your actual PostgreSQL credentials. - Import modules in
app.module.ts: Now, importConfigModuleandDatabaseModuleinto your rootAppModule.// src/app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ConfigModule } from '@nestjs/config'; import { DatabaseModule } from './database/database.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }), // Load .env file globally DatabaseModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
Step 5: Define a Sequelize Model
Let's create a simple User model.
- Generate a Module for Users:
nest g module users - Generate a Model and a Service:
nest g class users/user.model --no-spec # For the Sequelize model nest g service users/users --no-spec # For the service that interacts with the model - Define the User Model (
user model.ts):// src/users/user.model.ts import { Column, Model, Table, PrimaryKey, AutoIncrement } from 'sequelize-typescript'; @Table export class User extends Model { @PrimaryKey @AutoIncrement @Column id: number; @Column firstName: string; @Column lastName: string; @Column({ unique: true }) email: string; } - Register the Model in
UsersModule: We need to tell Sequelize about ourUsermodel within theUsersModule.// src/users/users.module.ts import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; import { User } from './user.model'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; @Module({ imports: [SequelizeModule.forFeature([User])], // Register the User model providers: [UsersService], controllers: [UsersController], exports: [UsersService], // If you want to use UsersService in other modules }) export class UsersModule {}
Step 6: Implement CRUD Operations
Now, let's build the API endpoints for our User model.
Update UsersService: The service will handle the logic for interacting with the User model.
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { User } from './user.model';
@Injectable()
export class UsersService {
constructor(
@InjectModel(User)
private userModel: typeof User,
) {}
async findAll(): Promise<User[]> {
return this.userModel.findAll();
}
async findOne(id: number): Promise {
return this.userModel.findByPk(id);
}
async create(user: Partial): Promise {
return this.userModel.create(user);
}
async update(id: number, user: Partial): Promise<[number, User[]]> {
return this.userModel.update(user, {
where: { id },
returning: true, // Return the updated record
});
}
async remove(id: number): Promise {
const user = await this.findOne(id);
await user.destroy();
}
}
- Generate a Controller for Users:
nest g controller users/users --no-spec - Update
UsersController: The controller will define the API routes and call the service methods.// src/users/users.controller.ts import { Controller, Get, Post, Put, Delete, Param, Body, HttpStatus, HttpCode } from '@nestjs/common'; import { UsersService } from './users.service'; import { User } from './user.model'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Get() async findAll(): Promise<User[]> { return this.usersService.findAll(); } @Get(':id') async findOne(@Param('id') id: string): Promise { return this.usersService.findOne(+id); } @Post() @HttpCode(HttpStatus.CREATED) async create(@Body() createUserDto: Partial): Promise { return this.usersService.create(createUserDto); } @Put(':id') async update(@Param('id') id: string, @Body() updateUserDto: Partial): Promise { const [numberOfAffectedRows, [updatedUser]] = await this.usersService.update(+id, updateUserDto); return updatedUser; // Return the updated user } @Delete(':id') @HttpCode(HttpStatus.NO_CONTENT) // 204 No Content for successful deletion async remove(@Param('id') id: string): Promise { await this.usersService.remove(+id); } } - Import
UsersModuleintoAppModule:// src/app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ConfigModule } from '@nestjs/config'; import { DatabaseModule } from './database/database.module'; import { UsersModule } from './users/users.module'; // Import UsersModule @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }), DatabaseModule, UsersModule, // Add UsersModule here ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
Step 7: Run Your Application
Now, start your NestJS application:
npm run start:dev
Your API should be running on http://localhost:3000.
Step 8: Test Your API
You can use tools like Postman, Insomnia, or curl to test your API.
Examples:
Create a User (POST request):POST http://localhost:3000/users Body (JSON):
JSON
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
}
- Get All Users (GET request):
GET http://localhost:3000/users - Get User by ID (GET request):
GET http://localhost:3000/users/1(replace 1 with an actual user ID)
Update a User (PUT request): PUT http://localhost:3000/users/1 Body (JSON):
JSON
{
"firstName": "smith"
}
- Delete a User (DELETE request):
DELETEhttp://localhost:3000/users/1
Next Steps
- Validation: Implement DTOs (Data Transfer Objects) and NestJS's
class-validatorandclass-transformerfor robust input validation. - Authentication & Authorization: Integrate JWT, Passport.js, or other authentication strategies.
- Error Handling: Implement global exception filters for consistent error responses.
- Migrations: For production environments, disable
synchronize: trueinSequelizeModuleand use Sequelize migrations for managing database schema changes. - Logging: Implement a proper logging strategy.
- Pagination & Filtering: For larger datasets, implement pagination, filtering, and sorting capabilities.
Conclusion
You've used NestJS, PostgreSQL, and Sequelize to successfully create a simple web API! This stack offers a robust and well-structured approach to creating scalable backend applications. You can create complex web services by utilising the architecture of NestJS, the ORM capabilities of Sequelize, and the dependability of PostgreSQL.