nestjs-boilerplate Documentation

NestJS boilerplate. Auth, TypeORM, Mongoose, Postgres, MongoDB, Mailing, I18N, Docker.

4,302 stars TypeScript TypeScript 1 file Β· ~11,368 tokens #auth#boilerplate#ci#crud#database#docker#e2e#i18n
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy nestjs-boilerplate on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name nestjs-boilerplate --restart always mongo:8.2.6
Configured Services & Ports
βš™οΈ mongo
image: mongo:8.2.6
βš™οΈ maildev
image: build locally
βš™οΈ api
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
services:
  mongo:
    image: mongo:8.2.6
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${DATABASE_USERNAME}
      MONGO_INITDB_ROOT_PASSWORD: ${DATABASE_PASSWORD}
    expose:
      - 27017

  maildev:
    build:
      context: .
      dockerfile: maildev.Dockerfile
    expose:
      - 1080
      - 1025

  # Uncomment to use redis
  # redis:
  #   image: redis:7-alpine
  #   expose:
  #     - 6379

  api:
    build:
      context: .
      dockerfile: document.e2e.Dockerfile
    env_file:
      - env-example-document

File: README.md

NestJS REST API boilerplate πŸ‡ΊπŸ‡¦



Description

NestJS REST API boilerplate for a typical project

Full documentation here

Demo: https://nestjs-boilerplate-test.herokuapp.com/docs

A fully compatible frontend boilerplate: https://github.com/brocoders/extensive-react-boilerplate

Belongs to the bc boilerplates ecosystem

https://github.com/user-attachments/assets/a66f114a-c714-4036-8eeb-20cbf04ae985

Table of Contents

Features

  • Database. Support TypeORM and Mongoose.
  • Seeding.
  • Config Service (@nestjs/config).
  • Mailing (nodemailer).
  • Sign in and sign up via email.
  • Social sign in (Apple, Facebook, Google).
  • Admin and User roles.
  • Internationalization/Translations (I18N) (nestjs-i18n).
  • File uploads. Support local and Amazon S3 drivers.
  • Swagger.
  • E2E and units tests.
  • Docker.
  • CI (Github Actions).

Contributors

Vladyslav Shchepotin🚧 πŸ“– πŸ’» SergeiLomakoπŸ’» Elena VlasenkoπŸ“– RodionπŸ’Ό

Support

If you seek consulting, support, or wish to collaborate, please contact us via [email protected]. For any inquiries regarding boilerplates, feel free to ask on GitHub Discussions or Discord.


File: docs/architecture.md

Architecture


Table of Contents


Hexagonal Architecture

NestJS Boilerplate is based on Hexagonal Architecture. This architecture is also known as Ports and Adapters.

Motivation

The main reason for using Hexagonal Architecture is to separate the business logic from the infrastructure. This separation allows us to easily change the database, the way of uploading files, or any other infrastructure without changing the business logic.

Description of the module structure

txt
.
β”œβ”€β”€ domain
β”‚   └── [DOMAIN_ENTITY].ts
β”œβ”€β”€ dto
β”‚   β”œβ”€β”€ create.dto.ts
β”‚   β”œβ”€β”€ find-all.dto.ts
β”‚   └── update.dto.ts
β”œβ”€β”€ infrastructure
β”‚   └── persistence
β”‚       β”œβ”€β”€ document
β”‚       β”‚   β”œβ”€β”€ document-persistence.module.ts
β”‚       β”‚   β”œβ”€β”€ entities
β”‚       β”‚   β”‚   └── [SCHEMA].ts
β”‚       β”‚   β”œβ”€β”€ mappers
β”‚       β”‚   β”‚   └── [MAPPER].ts
β”‚       β”‚   └── repositories
β”‚       β”‚       └── [ADAPTER].repository.ts
β”‚       β”œβ”€β”€ relational
β”‚       β”‚   β”œβ”€β”€ entities
β”‚       β”‚   β”‚   └── [ENTITY].ts
β”‚       β”‚   β”œβ”€β”€ mappers
β”‚       β”‚   β”‚   └── [MAPPER].ts
β”‚       β”‚   β”œβ”€β”€ relational-persistence.module.ts
β”‚       β”‚   └── repositories
β”‚       β”‚       └── [ADAPTER].repository.ts
β”‚       └── [PORT].repository.ts
β”œβ”€β”€ controller.ts
β”œβ”€β”€ module.ts
└── service.ts

[DOMAIN ENTITY].ts represents an entity used in the business logic. Domain entity has no dependencies on the database or any other infrastructure.

[SCHEMA].ts represents the database structure. It is used in the document-oriented database (MongoDB).

[ENTITY].ts represents the database structure. It is used in the relational database (PostgreSQL).

[MAPPER].ts is a mapper that converts database entity to domain entity and vice versa.

[PORT].repository.ts is a repository port that defines the methods for interacting with the database.

[ADAPTER].repository.ts is a repository that implements the [PORT].repository.ts. It is used to interact with the database.

infrastructure folder - contains all the infrastructure-related components such as persistence, uploader, senders, etc.

Each component has port and adapters. Port is interface that define the methods for interacting with the infrastructure. Adapters are implementations of the port.

Recommendations

Repository

Don't try to create universal methods in the repository because they are difficult to extend during the project's life. Instead of this create methods with a single responsibility.

typescript
// ❌
export class UsersRelationalRepository implements UserRepository {
  async find(condition: UniversalConditionInterface): Promise {
    // ...
  }
}

// βœ…
export class UsersRelationalRepository implements UserRepository {
  async findByEmail(email: string): Promise {
    // ...
  }
  
  async findByRoles(roles: string[]): Promise {
    // ...
  }
  
  async findByIds(ids: string[]): Promise {
    // ...
  }
}

FAQ

Is there a way to generate a new resource (controller, service, DTOs, etc) with Hexagonal Architecture?

Yes, you can use the CLI to generate a new resource with Hexagonal Architecture.



Previous: Installing and Running

Next: Command Line Interface


File: docs/auth.md

Auth

Table of Contents


General info

Auth via email flow

By default boilerplate used sign in and sign up via email and password.

mermaid
sequenceDiagram
    participant A as Fronted App (Web, Mobile, Desktop)
    participant B as Backend App

    A->>B: 1. Sign up via email and password
    A->>B: 2. Sign in via email and password
    B->>A: 3. Get a JWT token
    A->>B: 4. Make any requests using a JWT token

https://user-images.githubusercontent.com/6001723/224566194-1c1f4e98-5691-4703-b30e-92f99ec5d929.mp4

Auth via external services or social networks flow

Also you can sign up via another external services or social networks like Apple, Facebook and Google.

mermaid
sequenceDiagram
    participant B as External Auth Services (Apple, Google, etc)
    participant A as Fronted App (Web, Mobile, Desktop)
    participant C as Backend App

    A->>B: 1. Sign in through an external service
    B->>A: 2. Get Access Token
    A->>C: 3. Send Access Token to auth endpoint
    C->>A: 4. Get a JWT token
    A->>C: 5. Make any requests using a JWT token

For auth with external services or social networks you need:

  1. Sign in through an external service and get access token(s).

  2. Call one of endpoints with access token received in frontend app on 1-st step and get JWT token from the backend app.

    text
    POST /api/v1/auth/facebook/login
    
    POST /api/v1/auth/google/login
    
    POST /api/v1/auth/apple/login
  3. Make any requests using a JWT token


Configure Auth

  1. Generate secret keys for access token and refresh token:

    bash
    node -e "console.log('\nAUTH_JWT_SECRET=' + require('crypto').randomBytes(256).toString('base64') + '\n\nAUTH_REFRESH_SECRET=' + require('crypto').randomBytes(256).toString('base64') + '\n\nAUTH_FORGOT_SECRET=' + require('crypto').randomBytes(256).toString('base64') + '\n\nAUTH_CONFIRM_EMAIL_SECRET=' + require('crypto').randomBytes(256).toString('base64'));"
  2. Go to /.env and replace AUTH_JWT_SECRET and AUTH_REFRESH_SECRET with output from step 1.

    text
    AUTH_JWT_SECRET=HERE_SECRET_KEY_FROM_STEP_1
    AUTH_REFRESH_SECRET=HERE_SECRET_KEY_FROM_STEP_1

The app refuses to start with NODE_ENV=production while any AUTH_*_SECRET still has the placeholder value from env-example-*. In development it only logs a warning.

Auth via Apple

  1. Set up your service on Apple

  2. Change APPLE_APP_AUDIENCE in .env

    text
    APPLE_APP_AUDIENCE=["com.company", "com.company.web"]

Auth via Facebook

  1. Go to https://developers.facebook.com/apps/creation/ and create a new app

  2. Go to Settings -> Basic and get App ID and App Secret from your app

  3. Change FACEBOOK_APP_ID and FACEBOOK_APP_SECRET in .env

    text
    FACEBOOK_APP_ID=123
    FACEBOOK_APP_SECRET=abc

Auth via Google

  1. You need a CLIENT_ID, CLIENT_SECRET. You can find these pieces of information by going to the Developer Console, clicking your project (if doesn't have create it here https://console.cloud.google.com/projectcreate) -> APIs & services -> credentials.

  2. Change GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env

    text
    GOOGLE_CLIENT_ID=abc
    GOOGLE_CLIENT_SECRET=abc

About JWT strategy

In the validate method of the src/auth/strategies/jwt.strategy.ts file, you can see that we do not check if the user exists in the database because it is redundant, it may lose the benefits of the JWT approach and can affect the application performance.

To better understand how JWT works, watch the video explanation https://www.youtube.com/watch?v=Y2H3DXDeS3Q and read this article https://jwt.io/introduction/

typescript
// src/auth/strategies/jwt.strategy.ts

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
  // ...

  public validate(payload) {
    if (!payload.id) {
      throw new UnauthorizedException();
    }

    return payload;
  }
}

If you need to get full user information, get it in services.

Accessing the authenticated user

Whatever a strategy returns from validate() is what Passport assigns to request.user. To read it in a controller with type safety, annotate the request with RequestWithUser<T> from src/utils/types/request-with-user.type.ts:

typescript
export type RequestWithUser<TUser> = Request & {
  user: TUser;
};

For routes guarded by AuthGuard('jwt'), T is JwtPayloadType β€” so request.user.id, request.user.role and request.user.sessionId are all typed:

typescript
// src/auth/auth.controller.ts

@Get('me')
@UseGuards(AuthGuard('jwt'))
public me(
  @Request() request: RequestWithUser<JwtPayloadType>,
): Promise<NullableType> {
  return this.service.me(request.user);
}

POST /api/v1/auth/refresh is the exception: it is guarded by AuthGuard('jwt-refresh'), so a different strategy populates request.user and the payload is a JwtRefreshPayloadType instead:

typescript
@Post('refresh')
@UseGuards(AuthGuard('jwt-refresh'))
public refresh(
  @Request() request: RequestWithUser<JwtRefreshPayloadType>,
): Promise<RefreshResponseDto> {
  return this.service.refreshToken({
    sessionId: request.user.sessionId,
    hash: request.user.hash,
  });
}

Always pass the payload type matching the guard on the route β€” using JwtPayloadType on the refresh route (or vice versa) would describe fields that do not exist at runtime.

The same type works in guards and interceptors, where the request comes from the execution context instead. RolesGuard may be applied to routes without an auth guard, so it allows user to be absent:

typescript
// src/roles/roles.guard.ts

const request = context
  .switchToHttp()
  .getRequest<RequestWithUser<JwtPayloadType | undefined>>();

Refresh token flow

  1. On sign in (POST /api/v1/auth/email/login) you will receive token, tokenExpires and refreshToken in response.
  2. On each regular request you need to send token in Authorization header.
  3. If token is expired (check with tokenExpires property on client app) you need to send refreshToken to POST /api/v1/auth/refresh in Authorization header to refresh token. You will receive new token, tokenExpires and refreshToken in response.

Video example

https://github.com/brocoders/nestjs-boilerplate/assets/6001723/f6fdcc89-5ec6-472b-a6fc-d24178ad1bbb

Support login for multiple devices / Sessions

Boilerplate supports login for multiple devices with a Refresh Token flow. This is possible due to sessions. When a user logs in, a new session is created and stored in the database. The session record contains sessionId (id), userId, and hash.

On each POST /api/v1/auth/refresh request we check hash from the database with hash from the Refresh Token. If they are equal, we return new token, tokenExpires, and refreshToken. Then we update hash in the database to disallow the use of the previous Refresh Token.

Because every refresh also rotates the refreshToken, the AUTH_REFRESH_TOKEN_EXPIRES_IN default of 30d behaves as a rolling idle window: users who return at least once every 30 days never have to log in again, while a stolen or abandoned refresh token expires on its own.

Logout

  1. Call following endpoint:

    text
    POST /api/v1/auth/logout
  2. Remove access token and refresh token from your client app (cookies, localStorage, etc).

Security hardening

Security defaults the boilerplate ships with, and what is intentionally left to you:

  • Strong secrets are enforced in production. Startup fails when NODE_ENV=production and any AUTH_*_SECRET still contains the placeholder value from env-example-* (see Configure Auth).
  • HTTP security headers are set by helmet in src/main.ts. Content-Security-Policy is disabled because Swagger UI on /docs relies on inline scripts; enable it if you turn Swagger off.
  • CORS allow-list. APP_CORS_ORIGINS accepts a comma-separated list of origins (e.g. https://app.example.com,https://admin.example.com). When it is not set, FRONTEND_DOMAIN is used; when neither is set, any origin is allowed (*). Requests without an Origin header (mobile apps, curl, server-to-server) are not affected by CORS. Credentials mode stays off because auth uses the Authorization header, not cookies β€” which is also why the * fallback does not leak credentials.
  • Password reset links are single-use. The reset token is signed with a secret derived from the user's current password hash, so a link stops verifying the moment the password changes. All sessions are also revoked on reset.
  • Social providers must supply verified emails only. validateSocialLogin links accounts by email, so an unverified provider email would allow taking over an existing account. Every provider service reports an emailVerified flag on SocialInterface and rejects an unverified address with emailNotVerified; validateSocialLogin refuses to use an email whose flag is not true, so a provider added later fails closed until it sets the flag deliberately. Note that these are equality checks against true, not truthiness tests β€” provider payloads are passed through verbatim and Apple sends email_verified as 'true' | 'false' | boolean.
  • Social login cannot inherit an unconfirmed account. An account created by POST /api/v1/auth/email/register stays inactive until its confirmation link is followed, so its password was chosen by someone who never proved they own the address. When a social login matches such an account by verified email, its provider and socialId are moved to the social identity rather than the account being handed over as-is β€” otherwise registering a victim's address ahead of time would let an attacker keep password access to the account the victim later signs into (account pre-hijacking). validateLogin rejects any provider other than email, so the unproven password stops working. status is left untouched, because inactive also means "deactivated by an admin".
  • Uniform auth errors (opt-in). By default login and forgot-password return field-level errors (notFound, incorrectPassword, emailNotExists) that are convenient during development but reveal whether an email is registered. Set AUTH_UNIFORM_ERRORS=true to return a single incorrectEmailOrPassword error on login and a silent 204 from forgot-password for unknown emails.
  • Rate limiting is intentionally not bundled to keep setup easy (a production-grade limiter needs shared storage such as Redis). Add it at the proxy or platform level β€” nginx limit_req, Cloudflare, or your API gateway β€” especially for POST /api/v1/auth/email/login, POST /api/v1/auth/forgot/password, and POST /api/v1/auth/refresh. For single-instance deployments, @nestjs/throttler with its in-memory storage is an easy opt-in.
  • Possible future option: HttpOnly cookie sessions. Tokens are returned in the response body so every client (web, mobile, desktop) can use the same API via the Authorization header. A browser-only alternative is issuing tokens as HttpOnly cookies, which JavaScript (and therefore XSS) cannot read β€” at the cost of CSRF protection, CORS-with-credentials, and a second contract for non-browser clients. The boilerplate deliberately keeps the header-based contract.

Q&A

After `POST /api/v1/auth/logout` or removing session from the database, the user can still make requests with an `access token` for some time. Why?

It's because we use JWT. JWTs are stateless, so we can't revoke them, but don't worry, this is the correct behavior and the access token will expire after the time specified in AUTH_JWT_TOKEN_EXPIRES_IN (the default value is 15 minutes). If you still need to revoke JWT tokens immediately, you can check if a session exists in jwt.strategy.ts on each request. However, it's not recommended because it can affect the application's performance.


Previous: Database

Next: Serialization


File: docs/automatic-update-dependencies.md

Automatic update of dependencies

If you want to automatically update dependencies, you can connect Renovate for your project.


Previous: Benchmarking

Next: Translations


File: docs/benchmarking.md

Test benchmarking

Table of Contents

Apache Benchmark

bash
docker run --rm jordi/ab -n 100 -c 100 -T application/json -H "Authorization: Bearer USER_TOKEN" -v 2 http://<server_ip>:3001/api/v1/users

Previous: Tests

Next: Automatic update of dependencies


File: docs/cli.md

Command Line Interface (CLI)


Table of Contents


Generate resource

Generate resource with the following commands:

For document oriented database (MongoDB + Mongoose)

bash
npm run generate:resource:document -- --name ResourceName

Example:

bash
npm run generate:resource:document -- --name Category

For relational database (PostgreSQL + TypeORM)

bash
npm run generate:resource:relational -- --name ResourceName

Example:

bash
npm run generate:resource:relational -- --name Category

Video guideline for relational database (PostgreSQL + TypeORM)

https://github.com/user-attachments/assets/f7f91a7d-f9ff-4653-a78a-152ac5e7a95d

For both databases

bash
npm run generate:resource:all-db -- --name ResourceName

Example:

bash
npm run generate:resource:all-db -- --name Category

Add property to resource

Property for document oriented database (MongoDB + Mongoose)

bash
npm run add:property:to-document

Property for relational database (PostgreSQL + TypeORM)

bash
npm run add:property:to-relational

Video guideline how to add property for relational database (PostgreSQL + TypeORM)

https://github.com/user-attachments/assets/95b9d70a-70cf-442a-b8bf-a73d32810e0c

Property for both databases

bash
npm run add:property:to-all-db

Previous: Architecture

Next: Database


File: docs/database.md

Database

Table of Contents


About databases

Boilerplate supports two types of databases: PostgreSQL with TypeORM and MongoDB with Mongoose. You can choose one of them or use both in your project. The choice of database depends on the requirements of your project.

For support of both databases used Hexagonal Architecture.

Working with database schema (TypeORM)

Generate migration

  1. Create entity file with extension .entity.ts. For example post.entity.ts:

    ts
    // /src/posts/infrastructure/persistence/relational/entities/post.entity.ts
    
    import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
    import { EntityRelationalHelper } from '../../../../../utils/relational-entity-helper';
    
    @Entity()
    export class Post extends EntityRelationalHelper {
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column()
      title: string;
    
      @Column()
      body: string;
    
      // Here any fields that you need
    }
  2. Next, generate migration file:

    bash
    npm run migration:generate -- src/database/migrations/CreatePostTable
  3. Apply this migration to database via npm run migration:run.

Run migration

bash
npm run migration:run

Revert migration

bash
npm run migration:revert

Drop all tables in database

bash
npm run schema:drop

Working with database schema (Mongoose)

Create schema

  1. Create entity file with extension .schema.ts. For example post.schema.ts:

    ts
    // /src/posts/infrastructure/persistence/document/entities/post.schema.ts
    
    import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
    import { HydratedDocument } from 'mongoose';
    
    export type PostSchemaDocument = HydratedDocument;
    
    @Schema({
      timestamps: true,
      toJSON: {
        virtuals: true,
        getters: true,
      },
    })
    export class PostSchemaClass extends EntityDocumentHelper {
      @Prop()
      title: string;
    
      @Prop()
      body: string;
    
      // Here any fields that you need
    }
    
    export const PostSchema = SchemaFactory.createForClass(PostSchemaClass);

Seeding (TypeORM)

Creating seeds (TypeORM)

  1. Create seed file with npm run seed:create:relational -- --name Post. Where Post is name of entity.
  2. Go to src/database/seeds/relational/post/post-seed.service.ts.
  3. In run method extend your logic.
  4. Run npm run seed:run:relational

Run seed (TypeORM)

bash
npm run seed:run:relational

Factory and Faker (TypeORM)

  1. Install faker:

    bash
    npm i --save-dev @faker-js/faker
  2. Create src/database/seeds/relational/user/user.factory.ts:

    ts
    import { faker } from '@faker-js/faker';
    import { RoleEnum } from '../../../../roles/roles.enum';
    import { StatusEnum } from '../../../../statuses/statuses.enum';
    import { Injectable } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { Repository } from 'typeorm';
    import { RoleEntity } from '../../../../roles/infrastructure/persistence/relational/entities/role.entity';
    import { UserEntity } from '../../../../users/infrastructure/persistence/relational/entities/user.entity';
    import { StatusEntity } from '../../../../statuses/infrastructure/persistence/relational/entities/status.entity';
    
    @Injectable()
    export class UserFactory {
      constructor(
        @InjectRepository(UserEntity)
        private repositoryUser: Repository,
        @InjectRepository(RoleEntity)
        private repositoryRole: Repository<RoleEntity>,
        @InjectRepository(StatusEntity)
        private repositoryStatus: Repository<StatusEntity>,
      ) {}
    
      createRandomUser() {
        // Need for saving "this" context
        return () => {
          return this.repositoryUser.create({
            firstName: faker.person.firstName(),
            lastName: faker.person.lastName(),
            email: faker.internet.email(),
            password: faker.internet.password(),
            role: this.repositoryRole.create({
              id: RoleEnum.user,
              name: 'User',
            }),
            status: this.repositoryStatus.create({
              id: StatusEnum.active,
              name: 'Active',
            }),
          });
        };
      }
    }
  3. Make changes in src/database/seeds/relational/user/user-seed.service.ts:

    ts
    // Some code here...
    import { UserFactory } from './user.factory';
    import { faker } from '@faker-js/faker';
    
    @Injectable()
    export class UserSeedService {
      constructor(
        // Some code here...
        private userFactory: UserFactory,
      ) {}
    
      async run() {
        // Some code here...
    
        await this.repository.save(
          faker.helpers.multiple(this.userFactory.createRandomUser(), {
            count: 5,
          }),
        );
      }
    }
  4. Make changes in src/database/seeds/relational/user/user-seed.module.ts:

    ts
    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';
    
    import { UserSeedService } from './user-seed.service';
    import { UserFactory } from './user.factory';
    
    import { UserEntity } from '../../../../users/infrastructure/persistence/relational/entities/user.entity';
    import { RoleEntity } from '../../../../roles/infrastructure/persistence/relational/entities/role.entity';
    import { StatusEntity } from '../../../../statuses/infrastructure/persistence/relational/entities/status.entity';
    
    @Module({
      imports: [TypeOrmModule.forFeature([UserEntity, Role, Status])],
      providers: [UserSeedService, UserFactory],
      exports: [UserSeedService, UserFactory],
    })
    export class UserSeedModule {}
  5. Run seed:

    bash
    npm run seed:run

Seeding (Mongoose)

Creating seeds (Mongoose)

  1. Create seed file with npm run seed:create:document -- --name Post. Where Post is name of entity.
  2. Go to src/database/seeds/document/post/post-seed.service.ts.
  3. In run method extend your logic.
  4. Run npm run seed:run:document

Run seed (Mongoose)

bash
npm run seed:run:document

Performance optimization (PostgreSQL + TypeORM)

Indexes and Foreign Keys

Don't forget to create indexes on the Foreign Keys (FK) columns (if needed), because by default PostgreSQL does not automatically add indexes to FK.

Max connections

Set the optimal number of max connections to database for your application in /.env:

txt
DATABASE_MAX_CONNECTIONS=100

You can think of this parameter as how many concurrent database connections your application can handle.

Performance optimization (MongoDB + Mongoose)

Design schema

Designing schema for MongoDB is completely different from designing schema for relational databases. For best performance, you should design your schema according to:

  1. MongoDB Schema Design Anti-Patterns
  2. MongoDB Schema Design Best Practices

Switch PostgreSQL to MySQL

If you want to use MySQL instead of PostgreSQL, you can make the changes after following the complete guide given here.

Once you have completed all the steps, you should have a running app.

If you've made it this far, it only requires a few changes to switch from PostgreSQL to MySQL.

Change the .env file to the following:

env
DATABASE_TYPE=mysql
# set "localhost" if you are running app on local machine
# set "mysql" if you are running app on docker
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USERNAME=root
DATABASE_PASSWORD=secret
DATABASE_NAME=app

Change the docker-compose.yml to the following:

yml
services:
  mysql:
    image: mysql:9.2.0
    ports:
      - ${DATABASE_PORT}:3306
    volumes:
      - mysql-boilerplate-db:/var/lib/mysql
    environment:
      MYSQL_USER: ${DATABASE_USERNAME}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_DATABASE: ${DATABASE_NAME}

  # other services here...

volumes:
  # other volumes here...
  mysql-boilerplate-db:

After completing the above setup, run Docker with the following command:

bash
docker compose up -d mysql adminer maildev

All three services should be running as shown below:

Once your services are up and running, you're almost halfway through.

Now install the MySQL client:

bash
npm i mysql2 --save

Delete the existing migration file and generate a new one with the following script:

bash
npm run migration:generate -- src/database/migrations/newMigration --pretty=true

Run migrations:

bash
npm run migration:run

Run seeds:

bash
npm run seed:run:relational

Run the app in dev mode:

bash
npm run start:dev

Open http://localhost:3001

To set up Adminer:

Open the running port in your browser.
Open http://localhost:8080

Running App:


Previous: Command Line Interface

Next: Auth


File: docs/file-uploading.md

File uploading


Table of Contents


Drivers support

Out-of-box boilerplate supports the following drivers: local, s3, and s3-presigned. You can set it in the .env file, variable FILE_DRIVER. If you want to use another service for storing files, you can extend it.

For production we recommend using the "s3-presigned" driver to offload your server.


Uploading and attach file flow for `local` driver

Endpoint /api/v1/files/upload is used for uploading files, which returns File entity with id and path. After receiving File entity you can attach this to another entity.

An example of uploading an avatar to a user profile (local)

mermaid
sequenceDiagram
    participant A as Fronted App
    participant B as Backend App

    A->>B: Upload file via POST /api/v1/files/upload
    B->>A: Receive File entity with "id" and "path" properties
    note left of A: Attach File entity to User entity
    A->>B: Update user via PATCH /api/v1/auth/me

Video example

https://user-images.githubusercontent.com/6001723/224558636-d22480e4-f70a-4789-b6fc-6ea343685dc7.mp4

Uploading and attach file flow for `s3` driver

Endpoint /api/v1/files/upload is used for uploading files, which returns File entity with id and path. After receiving File entity you can attach this to another entity.

Configuration for `s3` driver

  1. Open https://s3.console.aws.amazon.com/s3/buckets

  2. Click "Create bucket"

  3. Create bucket (for example, your-unique-bucket-name)

  4. Open your bucket

  5. Click "Permissions" tab

  6. Find "Cross-origin resource sharing (CORS)" section

  7. Click "Edit"

  8. Paste the following configuration

    json
    [
      {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET"],
        "AllowedOrigins": ["*"],
        "ExposeHeaders": []
      }
    ]
  9. Click "Save changes"

  10. Update .env file with the following variables:

    dotenv
    FILE_DRIVER=s3
    ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID
    SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY
    AWS_S3_REGION=YOUR_AWS_S3_REGION
    AWS_DEFAULT_S3_BUCKET=YOUR_AWS_DEFAULT_S3_BUCKET

An example of uploading an avatar to a user profile (S3)

mermaid
sequenceDiagram
    participant A as Fronted App
    participant B as Backend App
    participant C as AWS S3

    A->>B: Upload file via POST /api/v1/files/upload
    B->>C: Upload file to S3
    B->>A: Receive File entity with "id" and "path" properties
    note left of A: Attach File entity to User entity
    A->>B: Update user via PATCH /api/v1/auth/me

Uploading and attach file flow for `s3-presigned` driver

Endpoint /api/v1/files/upload is used for uploading files. In this case /api/v1/files/upload receives only fileName property (without binary file), and returns the presigned URL and File entity with id and path. After receiving the presigned URL and File entity you need to upload your file to the presigned URL and after that attach File to another entity.

Configuration for `s3-presigned` driver

  1. Open https://s3.console.aws.amazon.com/s3/buckets

  2. Click "Create bucket"

  3. Create bucket (for example, your-unique-bucket-name)

  4. Open your bucket

  5. Click "Permissions" tab

  6. Find "Cross-origin resource sharing (CORS)" section

  7. Click "Edit"

  8. Paste the following configuration

    json
    [
      {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET", "PUT"],
        "AllowedOrigins": ["*"],
        "ExposeHeaders": []
      }
    ]

    For production we recommend to use more strict configuration:

    json
    [
      {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["PUT"],
        "AllowedOrigins": ["https://your-domain.com"],
        "ExposeHeaders": []
      },
       {
         "AllowedHeaders": ["*"],
         "AllowedMethods": ["GET"],
         "AllowedOrigins": ["*"],
         "ExposeHeaders": []
       }
    ]
  9. Click "Save changes"

  10. Update .env file with the following variables:

    dotenv
    FILE_DRIVER=s3-presigned
    ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID
    SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY
    AWS_S3_REGION=YOUR_AWS_S3_REGION
    AWS_DEFAULT_S3_BUCKET=YOUR_AWS_DEFAULT_S3_BUCKET

An example of uploading an avatar to a user profile (S3 Presigned URL)

mermaid
sequenceDiagram
    participant C as AWS S3
    participant A as Fronted App
    
    participant B as Backend App

    A->>B: Send file name (not binary file) via POST /api/v1/files/upload
    note right of B: Generate presigned URL
    B->>A: Receive presigned URL and File entity with "id" and "path" properties
    A->>C: Upload file to S3 via presigned URL
    note right of A: Attach File entity to User entity
    A->>B: Update user via PATCH /api/v1/auth/me

How to delete files?

We prefer not to delete files, as this may have negative experience during restoring data. Also for this reason we also use Soft-Delete approach in database. However, if you need to delete files you can create your own handler, cronjob, etc.


Previous: Serialization

Next: Tests


File: docs/installing-and-running.md

Installation

NestJS Boilerplate supports TypeORM and Mongoose for working with databases. By default, TypeORM uses PostgreSQL as the main database, but you can use any relational database.

Switching between TypeORM and Mongoose is implemented based on the Hexagonal Architecture. This makes it easy to choose the right database for your application.


Table of Contents


Comfortable development (PostgreSQL + TypeORM)

  1. Clone repository

    bash
    git clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-app
  2. Go to folder, and copy env-example-relational as .env.

    bash
    cd my-app/
    cp env-example-relational .env
  3. Change DATABASE_HOST=postgres to DATABASE_HOST=localhost

    Change MAIL_HOST=maildev to MAIL_HOST=localhost

  4. Run additional container:

    bash
    docker compose up -d postgres adminer maildev
  5. Install dependency

    bash
    npm install
  6. Run app configuration

    You should run this command only the first time on initialization of your project, all next time skip it.

    If you want to contribute to the boilerplate, you should NOT run this command.

    bash
    npm run app:config
  7. Run migrations

    bash
    npm run migration:run
  8. Run seeds

    bash
    npm run seed:run:relational
  9. Run app in dev mode

    bash
    npm run start:dev
  10. Open http://localhost:3001

Video guideline (PostgreSQL + TypeORM)

https://github.com/user-attachments/assets/136a16aa-f94a-4b20-8eaf-6b4262964315


Comfortable development (MongoDB + Mongoose)

  1. Clone repository

    bash
    git clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-app
  2. Go to folder, and copy env-example-document as .env.

    bash
    cd my-app/
    cp env-example-document .env
  3. Change DATABASE_URL=mongodb://mongo:27017 to DATABASE_URL=mongodb://localhost:27017

  4. Run additional container:

    bash
    docker compose -f docker-compose.document.yaml up -d mongo mongo-express maildev
  5. Install dependency

    bash
    npm install
  6. Run app configuration

    You should run this command only the first time on initialization of your project, all next time skip it.

    If you want to contribute to the boilerplate, you should NOT run this command.

    bash
    npm run app:config
  7. Run seeds

    bash
    npm run seed:run:document
  8. Run app in dev mode

    bash
    npm run start:dev
  9. Open http://localhost:3001


Quick run (PostgreSQL + TypeORM)

If you want quick run your app, you can use following commands:

  1. Clone repository

    bash
    git clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-app
  2. Go to folder, and copy env-example-relational as .env.

    bash
    cd my-app/
    cp env-example-relational .env
  3. Run containers

    bash
    docker compose up -d
  4. For check status run

    bash
    docker compose logs
  5. Open http://localhost:3001


Quick run (MongoDB + Mongoose)

If you want quick run your app, you can use following commands:

  1. Clone repository

    bash
    git clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-app
  2. Go to folder, and copy env-example-document as .env.

    bash
    cd my-app/
    cp env-example-document .env
  3. Run containers

    bash
    docker compose -f docker-compose.document.yaml up -d
  4. For check status run

    bash
    docker compose -f docker-compose.document.yaml logs
  5. Open http://localhost:3001



Previous: Introduction

Next: Architecture


File: docs/introduction.md

Introduction

Online demo

Demo: https://nestjs-boilerplate-test.herokuapp.com/docs

Frontend (React, Next.js): https://github.com/brocoders/extensive-react-boilerplate

https://github.com/user-attachments/assets/a66f114a-c714-4036-8eeb-20cbf04ae985

Features

  • Database. Support TypeORM and Mongoose.
  • Seeding.
  • Config Service (@nestjs/config).
  • Mailing (nodemailer).
  • Sign in and sign up via email.
  • Social sign in (Apple, Facebook, Google).
  • Admin and User roles.
  • Internationalization/Translations (I18N) (nestjs-i18n).
  • File uploads. Support local and Amazon S3 drivers.
  • Swagger.
  • Support E2E and units tests.
  • Docker.
  • CI (Github Actions).

Previous: Main

Next: Installing and Running