File: README.md
NestJS REST API boilerplate πΊπ¦
Description
NestJS REST API boilerplate for a typical project
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
.
βββ 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.
// β
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.
Links
- Dependency Inversion Principle with NestJS.
Previous: Installing and Running
Next: Command Line Interface
File: docs/auth.md
Auth
Table of Contents
- General info
- Configure Auth
- Auth via Apple
- Auth via Facebook
- Auth via Google
- About JWT strategy
- Accessing the authenticated user
- Refresh token flow
- Logout
- Security hardening
- Q&A
General info
Auth via email flow
By default boilerplate used sign in and sign up via email and password.
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 tokenhttps://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.
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 tokenFor auth with external services or social networks you need:
Sign in through an external service and get access token(s).
Call one of endpoints with access token received in frontend app on 1-st step and get JWT token from the backend app.
textPOST /api/v1/auth/facebook/login POST /api/v1/auth/google/login POST /api/v1/auth/apple/loginMake any requests using a JWT token
Configure Auth
Generate secret keys for
access tokenandrefresh token:bashnode -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'));"Go to
/.envand replaceAUTH_JWT_SECRETandAUTH_REFRESH_SECRETwith output from step 1.textAUTH_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=productionwhile anyAUTH_*_SECRETstill has the placeholder value fromenv-example-*. In development it only logs a warning.
Auth via Apple
Change
APPLE_APP_AUDIENCEin.envtextAPPLE_APP_AUDIENCE=["com.company", "com.company.web"]
Auth via Facebook
Go to https://developers.facebook.com/apps/creation/ and create a new app
Go to
Settings->Basicand getApp IDandApp Secretfrom your appChange
FACEBOOK_APP_IDandFACEBOOK_APP_SECRETin.envtextFACEBOOK_APP_ID=123 FACEBOOK_APP_SECRET=abc
Auth via Google
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.Change
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETin.envtextGOOGLE_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/
// 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:
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:
// 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:
@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:
// src/roles/roles.guard.ts
const request = context
.switchToHttp()
.getRequest<RequestWithUser<JwtPayloadType | undefined>>();Refresh token flow
- On sign in (
POST /api/v1/auth/email/login) you will receivetoken,tokenExpiresandrefreshTokenin response. - On each regular request you need to send
tokeninAuthorizationheader. - If
tokenis expired (check withtokenExpiresproperty on client app) you need to sendrefreshTokentoPOST /api/v1/auth/refreshinAuthorizationheader to refreshtoken. You will receive newtoken,tokenExpiresandrefreshTokenin 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
Call following endpoint:
textPOST /api/v1/auth/logoutRemove
access tokenandrefresh tokenfrom 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=productionand anyAUTH_*_SECRETstill contains the placeholder value fromenv-example-*(see Configure Auth). - HTTP security headers are set by helmet in
src/main.ts.Content-Security-Policyis disabled because Swagger UI on/docsrelies on inline scripts; enable it if you turn Swagger off. - CORS allow-list.
APP_CORS_ORIGINSaccepts a comma-separated list of origins (e.g.https://app.example.com,https://admin.example.com). When it is not set,FRONTEND_DOMAINis used; when neither is set, any origin is allowed (*). Requests without anOriginheader (mobile apps, curl, server-to-server) are not affected by CORS. Credentials mode stays off because auth uses theAuthorizationheader, 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.
validateSocialLoginlinks accounts by email, so an unverified provider email would allow taking over an existing account. Every provider service reports anemailVerifiedflag onSocialInterfaceand rejects an unverified address withemailNotVerified;validateSocialLoginrefuses to use an email whose flag is nottrue, so a provider added later fails closed until it sets the flag deliberately. Note that these are equality checks againsttrue, not truthiness tests β provider payloads are passed through verbatim and Apple sendsemail_verifiedas'true' | 'false' | boolean. - Social login cannot inherit an unconfirmed account. An account created by
POST /api/v1/auth/email/registerstaysinactiveuntil 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, itsproviderandsocialIdare 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).validateLoginrejects any provider other thanemail, so the unproven password stops working.statusis left untouched, becauseinactivealso 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. SetAUTH_UNIFORM_ERRORS=trueto return a singleincorrectEmailOrPassworderror on login and a silent204from 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 forPOST /api/v1/auth/email/login,POST /api/v1/auth/forgot/password, andPOST /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
Authorizationheader. A browser-only alternative is issuing tokens asHttpOnlycookies, 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
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/usersPrevious: 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)
npm run generate:resource:document -- --name ResourceNameExample:
npm run generate:resource:document -- --name CategoryFor relational database (PostgreSQL + TypeORM)
npm run generate:resource:relational -- --name ResourceNameExample:
npm run generate:resource:relational -- --name CategoryVideo guideline for relational database (PostgreSQL + TypeORM)
https://github.com/user-attachments/assets/f7f91a7d-f9ff-4653-a78a-152ac5e7a95d
For both databases
npm run generate:resource:all-db -- --name ResourceNameExample:
npm run generate:resource:all-db -- --name CategoryAdd property to resource
Property for document oriented database (MongoDB + Mongoose)
npm run add:property:to-documentProperty for relational database (PostgreSQL + TypeORM)
npm run add:property:to-relationalVideo 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
npm run add:property:to-all-dbPrevious: Architecture
Next: Database
File: docs/database.md
Database
Table of Contents
- About databases
- Working with database schema (TypeORM)
- Working with database schema (Mongoose)
- Seeding (TypeORM)
- Seeding (Mongoose)
- Performance optimization (PostgreSQL + TypeORM)
- Performance optimization (MongoDB + Mongoose)
- Switch PostgreSQL to MySQL
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
Create entity file with extension
.entity.ts. For examplepost.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 }Next, generate migration file:
bashnpm run migration:generate -- src/database/migrations/CreatePostTableApply this migration to database via npm run migration:run.
Run migration
npm run migration:runRevert migration
npm run migration:revertDrop all tables in database
npm run schema:dropWorking with database schema (Mongoose)
Create schema
Create entity file with extension
.schema.ts. For examplepost.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)
- Create seed file with
npm run seed:create:relational -- --name Post. WherePostis name of entity. - Go to
src/database/seeds/relational/post/post-seed.service.ts. - In
runmethod extend your logic. - Run npm run seed:run:relational
Run seed (TypeORM)
npm run seed:run:relationalFactory and Faker (TypeORM)
Install faker:
bashnpm i --save-dev @faker-js/fakerCreate
src/database/seeds/relational/user/user.factory.ts:tsimport { 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', }), }); }; } }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, }), ); } }Make changes in
src/database/seeds/relational/user/user-seed.module.ts:tsimport { 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 {}Run seed:
bashnpm run seed:run
Seeding (Mongoose)
Creating seeds (Mongoose)
- Create seed file with
npm run seed:create:document -- --name Post. WherePostis name of entity. - Go to
src/database/seeds/document/post/post-seed.service.ts. - In
runmethod extend your logic. - Run npm run seed:run:document
Run seed (Mongoose)
npm run seed:run:documentPerformance 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:
DATABASE_MAX_CONNECTIONS=100You 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:
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:
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=appChange the docker-compose.yml to the following:
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:
docker compose up -d mysql adminer maildevAll 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:
npm i mysql2 --saveDelete the existing migration file and generate a new one with the following script:
npm run migration:generate -- src/database/migrations/newMigration --pretty=trueRun migrations:
npm run migration:runRun seeds:
npm run seed:run:relationalRun the app in dev mode:
npm run start:devTo 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
- Uploading and attach file flow for
localdriver - Uploading and attach file flow for
s3driver - Uploading and attach file flow for
s3-presigneddriver - How to delete files?
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)
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/meVideo 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
Click "Create bucket"
Create bucket (for example,
your-unique-bucket-name)Open your bucket
Click "Permissions" tab
Find "Cross-origin resource sharing (CORS)" section
Click "Edit"
Paste the following configuration
json[ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET"], "AllowedOrigins": ["*"], "ExposeHeaders": [] } ]Click "Save changes"
Update
.envfile with the following variables:dotenvFILE_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)
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/meUploading 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
Click "Create bucket"
Create bucket (for example,
your-unique-bucket-name)Open your bucket
Click "Permissions" tab
Find "Cross-origin resource sharing (CORS)" section
Click "Edit"
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": [] } ]Click "Save changes"
Update
.envfile with the following variables:dotenvFILE_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)
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/meHow 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)
- Comfortable development (MongoDB + Mongoose)
- Quick run (PostgreSQL + TypeORM)
- Quick run (MongoDB + Mongoose)
- Links
Comfortable development (PostgreSQL + TypeORM)
Clone repository
bashgit clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-appGo to folder, and copy
env-example-relationalas.env.bashcd my-app/ cp env-example-relational .envChange
DATABASE_HOST=postgrestoDATABASE_HOST=localhostChange
MAIL_HOST=maildevtoMAIL_HOST=localhostRun additional container:
bashdocker compose up -d postgres adminer maildevInstall dependency
bashnpm installRun 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.
bashnpm run app:configRun migrations
bashnpm run migration:runRun seeds
bashnpm run seed:run:relationalRun app in dev mode
bashnpm run start:dev
Video guideline (PostgreSQL + TypeORM)
https://github.com/user-attachments/assets/136a16aa-f94a-4b20-8eaf-6b4262964315
Comfortable development (MongoDB + Mongoose)
Clone repository
bashgit clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-appGo to folder, and copy
env-example-documentas.env.bashcd my-app/ cp env-example-document .envChange
DATABASE_URL=mongodb://mongo:27017toDATABASE_URL=mongodb://localhost:27017Run additional container:
bashdocker compose -f docker-compose.document.yaml up -d mongo mongo-express maildevInstall dependency
bashnpm installRun 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.
bashnpm run app:configRun seeds
bashnpm run seed:run:documentRun app in dev mode
bashnpm run start:dev
Quick run (PostgreSQL + TypeORM)
If you want quick run your app, you can use following commands:
Clone repository
bashgit clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-appGo to folder, and copy
env-example-relationalas.env.bashcd my-app/ cp env-example-relational .envRun containers
bashdocker compose up -dFor check status run
bashdocker compose logs
Quick run (MongoDB + Mongoose)
If you want quick run your app, you can use following commands:
Clone repository
bashgit clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-appGo to folder, and copy
env-example-documentas.env.bashcd my-app/ cp env-example-document .envRun containers
bashdocker compose -f docker-compose.document.yaml up -dFor check status run
bashdocker compose -f docker-compose.document.yaml logs
Links
- Swagger (API docs): http://localhost:3001/docs
- Adminer (client for DB): http://localhost:8080
- MongoDB Express (client for DB): http://localhost:8081/
- Maildev: http://localhost:1080
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