├── src ├── main │ ├── routes │ │ ├── index.ts │ │ └── user.ts │ ├── adapters │ │ ├── index.ts │ │ └── nest-router.ts │ └── factories │ │ ├── index.ts │ │ ├── user.module.ts │ │ └── app.module.ts ├── application │ ├── errors │ │ ├── index.ts │ │ └── http.ts │ ├── helpers │ │ ├── index.ts │ │ └── http.ts │ └── controllers │ │ ├── index.ts │ │ ├── controller-handlers.module.ts │ │ ├── controller.ts │ │ ├── load-user-handler.ts │ │ └── create-user-handler.ts ├── domain │ ├── entities │ │ ├── index.ts │ │ └── user.ts │ ├── contracts │ │ └── repos │ │ │ ├── index.ts │ │ │ └── user.ts │ └── use-cases │ │ ├── index.ts │ │ ├── use-cases.module.ts │ │ ├── load-users.service.ts │ │ └── create-user.service.ts ├── infra │ └── db │ │ └── pg │ │ ├── entities │ │ ├── index.ts │ │ ├── phone.entity.ts │ │ └── user.entity.ts │ │ └── repos │ │ ├── index.ts │ │ ├── user-repos.module.ts │ │ └── user-pg-repo.ts └── main.ts ├── test ├── domain │ ├── mocks │ │ ├── index.ts │ │ └── user.ts │ └── use-cases │ │ ├── load-users.spec.ts │ │ └── create-user.spec.ts ├── application │ ├── mocks │ │ ├── index.ts │ │ └── user.ts │ └── controllers │ │ ├── create-user-handler.spec.ts │ │ ├── load-users-handler.spec.ts │ │ └── controller.spec.ts ├── infra │ └── db │ │ └── pg │ │ ├── helper │ │ ├── index.ts │ │ └── pg-helper.ts │ │ └── repos │ │ └── user.spec.ts └── main │ ├── routes │ ├── user.spec.ts │ └── user-e2e.test.ts │ └── adapters │ └── nest-router.spec.ts ├── .eslintignore ├── db ├── .docker │ ├── Dockerfile │ └── init.sql └── migrations │ └── 1646328050686-create-user-and-photo-table.ts ├── .husky ├── pre-commit ├── pre-push └── commit-msg ├── nest-cli.json ├── .lintstagedrc.json ├── .env.default ├── jest-unit-config.js ├── public └── clean-architecture.jpg ├── tsconfig.build.json ├── jest-integration-config.js ├── docker-compose.yml ├── ormconfig.js ├── .gitignore ├── .eslintrc.js ├── tsconfig.json ├── jest.config.js ├── package.json └── README.md /src/main/routes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | -------------------------------------------------------------------------------- /test/domain/mocks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | -------------------------------------------------------------------------------- /src/application/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './http' 2 | -------------------------------------------------------------------------------- /src/domain/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | -------------------------------------------------------------------------------- /test/application/mocks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | -------------------------------------------------------------------------------- /src/application/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './http' 2 | -------------------------------------------------------------------------------- /src/domain/contracts/repos/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user' 2 | -------------------------------------------------------------------------------- /src/main/adapters/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nest-router' 2 | -------------------------------------------------------------------------------- /test/infra/db/pg/helper/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pg-helper' 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | .idea 5 | .vscode 6 | -------------------------------------------------------------------------------- /db/.docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres 2 | COPY init.sql /docker-entrypoint-initdb.d/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn test:cov 5 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | .git/hooks/commit-msg 5 | -------------------------------------------------------------------------------- /src/infra/db/pg/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.entity' 2 | export * from './phone.entity' 3 | -------------------------------------------------------------------------------- /src/main/factories/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.module' 2 | 3 | export * from './app.module' 4 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.ts": [ 3 | "npm run lint:fix", 4 | "npm run test:staged" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/infra/db/pg/repos/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user-pg-repo' 2 | export * from './user-repos.module' 3 | -------------------------------------------------------------------------------- /src/domain/entities/user.ts: -------------------------------------------------------------------------------- 1 | export type User = { 2 | id: number 3 | name: string 4 | email: string 5 | } 6 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | DB_HOST=localhost 2 | DB_PORT=5432 3 | DB_USERNAME=root 4 | DB_PASSWORD=root 5 | DB_DATABASE=nestjs-db 6 | -------------------------------------------------------------------------------- /jest-unit-config.js: -------------------------------------------------------------------------------- 1 | const config = require('./jest.config') 2 | config.testMatch = ['**/*.spec.ts'] 3 | module.exports = config 4 | -------------------------------------------------------------------------------- /public/clean-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matheusalxds/clean-architecture-nestjs/HEAD/public/clean-architecture.jpg -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /jest-integration-config.js: -------------------------------------------------------------------------------- 1 | const config = require('./jest.config') 2 | config.testMatch = ['**/*-e2e.test.ts'] 3 | module.exports = config 4 | -------------------------------------------------------------------------------- /src/domain/use-cases/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-user.service' 2 | export * from './load-users.service' 3 | 4 | export * from './use-cases.module' 5 | -------------------------------------------------------------------------------- /src/application/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './controller' 2 | export * from './create-user-handler' 3 | export * from './load-user-handler' 4 | 5 | export * from './controller-handlers.module' 6 | -------------------------------------------------------------------------------- /test/domain/mocks/user.ts: -------------------------------------------------------------------------------- 1 | import { mockUserInput } from '@/test/application/mocks' 2 | import { User } from '@/domain/entities' 3 | 4 | export const mockUser = (): User => ({ id: 1, ...mockUserInput() }) 5 | -------------------------------------------------------------------------------- /src/application/errors/http.ts: -------------------------------------------------------------------------------- 1 | export class ServerError extends Error { 2 | constructor (error: Error) { 3 | super('Server failed. Try again soon') 4 | this.name = 'ServerError' 5 | this.stack = error.stack 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/application/mocks/user.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserHandler } from '@/application/controllers' 2 | 3 | export const mockUserInput = (): CreateUserHandler.Input => ({ 4 | email: 'matheus.alxds@gmail.com', 5 | name: 'Matheus Alexandre' 6 | }) 7 | -------------------------------------------------------------------------------- /db/.docker/init.sql: -------------------------------------------------------------------------------- 1 | CREATE USER user_nestjs_db WITH PASSWORD 'abc123'; 2 | CREATE USER user_geo_migration WITH PASSWORD '321abc'; 3 | GRANT ALL PRIVILEGES ON DATABASE "nestjs-db" to user_nestjs_db; 4 | GRANT ALL PRIVILEGES ON DATABASE "nestjs-db" to user_geo_migration; 5 | -------------------------------------------------------------------------------- /src/infra/db/pg/repos/user-repos.module.ts: -------------------------------------------------------------------------------- 1 | import { UserPgRepo } from '@/infra/db/pg/repos' 2 | 3 | import { Module } from '@nestjs/common' 4 | 5 | @Module({ 6 | providers: [UserPgRepo], 7 | exports: [UserPgRepo] 8 | }) 9 | export class UserReposModule {} 10 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { AppModule } from '@/main/factories' 3 | 4 | async function bootstrap () { 5 | const app = await NestFactory.create(AppModule, {}) 6 | await app.listen(3000) 7 | } 8 | 9 | bootstrap() 10 | .catch(e => console.log('error', e)) 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | db: 5 | container_name: pg-container 6 | build: db/.docker 7 | restart: always 8 | environment: 9 | POSTGRES_DB: 'nestjs-db' 10 | POSTGRES_PASSWORD: root 11 | POSTGRES_USER: root 12 | ports: 13 | - "5432:5432" 14 | -------------------------------------------------------------------------------- /src/main/factories/user.module.ts: -------------------------------------------------------------------------------- 1 | import { ControllerHandlersModule } from '@/application/controllers' 2 | import { UserRoutes } from '@/main/routes' 3 | 4 | import { Module } from '@nestjs/common' 5 | 6 | @Module({ 7 | imports: [ControllerHandlersModule], 8 | controllers: [UserRoutes] 9 | }) 10 | export class UserModule {} 11 | -------------------------------------------------------------------------------- /src/domain/use-cases/use-cases.module.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserUC, LoadUsersUC } from '@/domain/use-cases' 2 | import { UserReposModule } from '@/infra/db/pg/repos' 3 | 4 | import { Module } from '@nestjs/common' 5 | 6 | @Module({ 7 | imports: [UserReposModule], 8 | providers: [CreateUserUC, LoadUsersUC], 9 | exports: [CreateUserUC, LoadUsersUC] 10 | }) 11 | export class UseCasesModule {} 12 | -------------------------------------------------------------------------------- /ormconfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'postgres', 3 | host: process.env.DB_HOST, 4 | port: process.env.DB_PORT, 5 | username: process.env.DB_USERNAME, 6 | password: process.env.DB_PASSWORD, 7 | database: process.env.DB_DATABASE, 8 | entities: ['dist/src/infra/db/pg/entities/index.js'], 9 | migrations: ['dist/db/migrations/*.js'], 10 | cli: { 11 | migrationsDir: 'db/migrations' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/application/controllers/controller-handlers.module.ts: -------------------------------------------------------------------------------- 1 | import { UseCasesModule } from '@/domain/use-cases' 2 | import { CreateUserHandler, LoadUsersHandler } from './' 3 | 4 | import { Module } from '@nestjs/common' 5 | 6 | @Module({ 7 | imports: [UseCasesModule], 8 | exports: [CreateUserHandler, LoadUsersHandler], 9 | providers: [CreateUserHandler, LoadUsersHandler] 10 | }) 11 | export class ControllerHandlersModule {} 12 | -------------------------------------------------------------------------------- /src/application/helpers/http.ts: -------------------------------------------------------------------------------- 1 | import { ServerError } from '@/application/errors' 2 | 3 | export type HttpResponse = { 4 | statusCode: number 5 | data: T 6 | } 7 | 8 | export const serverError = (error: Error): HttpResponse => ({ 9 | statusCode: 500, 10 | data: new ServerError(error) 11 | }) 12 | 13 | export const ok = (data: T): HttpResponse => ({ 14 | statusCode: 200, 15 | data 16 | }) 17 | -------------------------------------------------------------------------------- /src/domain/contracts/repos/user.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@/domain/entities' 2 | 3 | export interface UserRepository { 4 | create: (params: Create.Input) => Promise 5 | load: () => Promise 6 | } 7 | 8 | export namespace Create { 9 | export type Input = { name: string, email: string} 10 | export type Output = number 11 | } 12 | 13 | export namespace Load { 14 | export type Output = User[] 15 | } 16 | -------------------------------------------------------------------------------- /src/main/adapters/nest-router.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@/application/controllers' 2 | 3 | type Adapter = (controller: Controller) => any 4 | 5 | export const adaptNestRouter: Adapter = (controller: Controller) => async (body, res) => { 6 | const { statusCode, data = {} } = await controller.handle(body) 7 | const json = [200, 204].includes(statusCode) ? data : { error: data.message } 8 | return res.status(statusCode).json(json) 9 | } 10 | -------------------------------------------------------------------------------- /src/infra/db/pg/entities/phone.entity.ts: -------------------------------------------------------------------------------- 1 | import { UserEntity } from '@/infra/db/pg/entities' 2 | 3 | import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, ManyToOne } from 'typeorm' 4 | 5 | @Entity() 6 | export class PhoneEntity extends BaseEntity { 7 | @PrimaryGeneratedColumn() 8 | id: number 9 | 10 | @Column({ nullable: true }) 11 | number: number 12 | 13 | @ManyToOne(() => UserEntity, user => user.phones) 14 | user: UserEntity 15 | } 16 | -------------------------------------------------------------------------------- /src/infra/db/pg/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { PhoneEntity } from '@/infra/db/pg/entities' 2 | 3 | import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, OneToMany } from 'typeorm' 4 | 5 | @Entity({ name: 'user' }) 6 | export class UserEntity extends BaseEntity { 7 | @PrimaryGeneratedColumn() 8 | id: number 9 | 10 | @Column() 11 | name: string 12 | 13 | @Column() 14 | email: string 15 | 16 | @OneToMany(() => PhoneEntity, phone => phone.user) 17 | phones: PhoneEntity[] 18 | } 19 | -------------------------------------------------------------------------------- /src/domain/use-cases/load-users.service.ts: -------------------------------------------------------------------------------- 1 | import { UserPgRepo } from '@/infra/db/pg/repos' 2 | import { Load } from '@/domain/contracts/repos' 3 | 4 | import { Injectable } from '@nestjs/common' 5 | 6 | @Injectable() 7 | export class LoadUsersUC { 8 | constructor (private readonly userRepo: UserPgRepo) {} 9 | 10 | async execute (): Promise { 11 | return this.userRepo.load() 12 | } 13 | } 14 | 15 | export namespace LoadUsersUC { 16 | export type Output = Load.Output 17 | } 18 | -------------------------------------------------------------------------------- /src/application/controllers/controller.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse, serverError } from '@/application/helpers' 2 | 3 | export abstract class Controller { 4 | abstract perform (input: any): Promise 5 | 6 | async handle (input: any): Promise { 7 | try { 8 | return await this.perform(input) 9 | } catch (error) { 10 | return serverError(error as Error) 11 | } 12 | } 13 | } 14 | 15 | export namespace Controller { 16 | export type Output = HttpResponse 17 | } 18 | -------------------------------------------------------------------------------- /src/main/factories/app.module.ts: -------------------------------------------------------------------------------- 1 | import { UserModule } from '@/main/factories' 2 | 3 | import { Module } from '@nestjs/common' 4 | import { TypeOrmModule } from '@nestjs/typeorm' 5 | import { ConfigModule } from '@nestjs/config' 6 | import { RouterModule } from '@nestjs/core' 7 | 8 | @Module({ 9 | imports: [ 10 | ConfigModule.forRoot(), 11 | TypeOrmModule.forRoot({ keepConnectionAlive: true }), 12 | RouterModule.register([ 13 | { path: 'users', module: UserModule } 14 | ]), 15 | UserModule 16 | ] 17 | }) 18 | export class AppModule {} 19 | -------------------------------------------------------------------------------- /src/domain/use-cases/create-user.service.ts: -------------------------------------------------------------------------------- 1 | import { UserPgRepo } from '@/infra/db/pg/repos' 2 | import { Create } from '@/domain/contracts/repos' 3 | 4 | import { Injectable } from '@nestjs/common' 5 | 6 | @Injectable() 7 | export class CreateUserUC { 8 | constructor (private readonly userRepo: UserPgRepo) {} 9 | 10 | async execute (input: CreateUserUC.Input): Promise { 11 | return this.userRepo.create(input) 12 | } 13 | } 14 | 15 | export namespace CreateUserUC { 16 | export type Input = Create.Input 17 | export type Output = Create.Output 18 | } 19 | -------------------------------------------------------------------------------- /src/application/controllers/load-user-handler.ts: -------------------------------------------------------------------------------- 1 | import { ok } from '@/application/helpers' 2 | import { LoadUsersUC } from '@/domain/use-cases' 3 | import { Controller } from '@/application/controllers' 4 | 5 | import { Injectable } from '@nestjs/common' 6 | 7 | @Injectable() 8 | export class LoadUsersHandler extends Controller { 9 | constructor (private readonly loadUsersUC: LoadUsersUC) { 10 | super() 11 | } 12 | 13 | async perform (): Promise { 14 | const httpResponse = await this.loadUsersUC.execute() 15 | return ok(httpResponse) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # env files 2 | .env 3 | 4 | # IDEs 5 | .idea 6 | 7 | # compiled output 8 | /dist 9 | /node_modules 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | pnpm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | lerna-debug.log* 19 | 20 | # OS 21 | .DS_Store 22 | 23 | # Tests 24 | /coverage 25 | /.nyc_output 26 | 27 | # IDEs and editors 28 | /.idea 29 | .project 30 | .classpath 31 | .c9/ 32 | *.launch 33 | .settings/ 34 | *.sublime-workspace 35 | 36 | # IDE - VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: './tsconfig.json' 4 | }, 5 | extends: 'standard-with-typescript', 6 | plugins: ['unused-imports'], 7 | rules: { 8 | '@typescript-eslint/interface-name-prefix': 'off', 9 | '@typescript-eslint/explicit-function-return-type': 'off', 10 | '@typescript-eslint/explicit-module-boundary-types': 'off', 11 | '@typescript-eslint/no-explicit-any': 'off', 12 | '@typescript-eslint/consistent-type-definitions': 'off', 13 | '@typescript-eslint/no-namespace': 'off', 14 | '@typescript-eslint/return-await': 'off', 15 | 'import/export': 'off', 16 | 'unused-imports/no-unused-imports': 'error' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/infra/db/pg/repos/user-pg-repo.ts: -------------------------------------------------------------------------------- 1 | import { Create, Load, UserRepository } from '@/domain/contracts/repos' 2 | import { UserEntity } from '@/infra/db/pg/entities' 3 | 4 | import { Injectable } from '@nestjs/common' 5 | import { getRepository } from 'typeorm' 6 | 7 | @Injectable() 8 | export class UserPgRepo implements UserRepository { 9 | async create (params: Create.Input): Promise { 10 | const pgRepo = getRepository(UserEntity) 11 | const inserted = await pgRepo.insert(params) 12 | const [newUser] = inserted.generatedMaps 13 | return newUser.id 14 | } 15 | 16 | async load (): Promise { 17 | const pgRepo = getRepository(UserEntity) 18 | return pgRepo.find() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/application/controllers/create-user-handler.ts: -------------------------------------------------------------------------------- 1 | import { ok } from '@/application/helpers' 2 | import { CreateUserUC } from '@/domain/use-cases' 3 | import { Controller } from '@/application/controllers' 4 | 5 | import { Injectable } from '@nestjs/common' 6 | 7 | @Injectable() 8 | export class CreateUserHandler extends Controller { 9 | constructor (private readonly createUserUC: CreateUserUC) { 10 | super() 11 | } 12 | 13 | async perform (input: CreateUserHandler.Input): Promise { 14 | const httpResponse = await this.createUserUC.execute(input) 15 | return ok(httpResponse) 16 | } 17 | } 18 | 19 | export namespace CreateUserHandler { 20 | export type Input = { name: string, email: string } 21 | } 22 | -------------------------------------------------------------------------------- /test/infra/db/pg/helper/pg-helper.ts: -------------------------------------------------------------------------------- 1 | import { IBackup, IMemoryDb, newDb } from 'pg-mem' 2 | 3 | export const PgTestHelper = { 4 | db: null as unknown as IMemoryDb, 5 | connection: null as any, 6 | backup: null as unknown as IBackup, 7 | async connect (entities?: any[]) { 8 | this.db = newDb() 9 | this.db.public.registerFunction({ implementation: () => 'test', name: 'current_database' }) 10 | this.connection = await this.db.adapters.createTypeormConnection({ 11 | type: 'postgres', 12 | entities: entities ?? ['src/infra/db/pg/entities/index.ts'] 13 | }) 14 | await this.sync() 15 | this.backup = this.db.backup() 16 | }, 17 | restore () { 18 | this.backup.restore() 19 | }, 20 | async disconnect () { 21 | await this.connection.close() 22 | }, 23 | async sync () { 24 | await this.connection.synchronize() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/routes/user.ts: -------------------------------------------------------------------------------- 1 | import { adaptNestRouter } from '@/main/adapters' 2 | import { CreateUserHandler, LoadUsersHandler } from '@/application/controllers' 3 | 4 | import { Body, Controller, Get, Post, Res } from '@nestjs/common' 5 | import { RequestHandler, Response } from 'express' 6 | 7 | @Controller() 8 | export class UserRoutes { 9 | constructor ( 10 | private readonly createUserHandler: CreateUserHandler, 11 | private readonly loadUsersHandler: LoadUsersHandler 12 | ) {} 13 | 14 | @Post() 15 | async create (@Body() body: CreateUserHandler.Input, @Res() res: Response): Promise { 16 | return adaptNestRouter(this.createUserHandler)(body, res) 17 | } 18 | 19 | @Get() 20 | async load (@Body() body: CreateUserHandler.Input, @Res() res: Response): Promise { 21 | return adaptNestRouter(this.loadUsersHandler)(body, res) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "src", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "strictPropertyInitialization": false, 19 | "forceConsistentCasingInFileNames": false, 20 | "noFallthroughCasesInSwitch": false, 21 | "strict": true, 22 | "rootDirs": ["src", "test", "db/migrations"], 23 | "paths": { 24 | "@/*": ["*"], 25 | "@/test/*": ["../test/*"] 26 | } 27 | }, 28 | "include": ["src", "test", "db/migrations"] 29 | } 30 | -------------------------------------------------------------------------------- /db/migrations/1646328050686-create-user-and-photo-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm' 2 | 3 | export class createUserAndPhotoTable1646328050686 implements MigrationInterface { 4 | name = 'createUserAndPhotoTable1646328050686' 5 | 6 | public async up (queryRunner: QueryRunner): Promise { 7 | await queryRunner.query('CREATE TABLE "user" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "email" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))') 8 | await queryRunner.query('CREATE TABLE "phone" ("id" SERIAL NOT NULL, "number" integer, "userId" integer, CONSTRAINT "PK_f35e6ee6c1232ce6462505c2b25" PRIMARY KEY ("id"))') 9 | await queryRunner.query('ALTER TABLE "phone" ADD CONSTRAINT "FK_260d7031e6bd9ed4fbcd2dd3ad6" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION') 10 | } 11 | 12 | public async down (queryRunner: QueryRunner): Promise { 13 | await queryRunner.query('ALTER TABLE "phone" DROP CONSTRAINT "FK_260d7031e6bd9ed4fbcd2dd3ad6"') 14 | await queryRunner.query('DROP TABLE "phone"') 15 | await queryRunner.query('DROP TABLE "user"') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'json', 'ts'], 3 | // An array of glob patterns indicating a set of files for which coverage information should be collected 4 | collectCoverageFrom: [ 5 | '/src/**/*.ts', 6 | '!/src/main/**', 7 | '!/src/main.ts', 8 | '!/src/**/index.ts' 9 | ], 10 | 11 | // The directory where Jest should output its coverage files 12 | coverageDirectory: 'coverage', 13 | 14 | // Indicates which provider should be used to instrument code for coverage 15 | coverageProvider: 'babel', 16 | 17 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 18 | moduleNameMapper: { 19 | '@/test/(.+)': '/test/$1', 20 | '@/(.+)': '/src/$1' 21 | }, 22 | 23 | // A list of paths to directories that Jest should use to search for files in 24 | roots: [ 25 | '/src', 26 | '/test', 27 | '/db/migrations' 28 | ], 29 | 30 | // A map from regular expressions to paths to transformers 31 | transform: { 32 | '\\.ts$': 'ts-jest' 33 | }, 34 | clearMocks: true, 35 | testMatch: ['**/*.(spec|test).ts'] 36 | } 37 | -------------------------------------------------------------------------------- /test/application/controllers/create-user-handler.spec.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserHandler, Controller } from '@/application/controllers' 2 | import { CreateUserUC } from '@/domain/use-cases' 3 | import { mockUserInput } from '@/test/application/mocks' 4 | 5 | import { mock, MockProxy } from 'jest-mock-extended' 6 | 7 | describe('CreateUserController', () => { 8 | let createUser: MockProxy 9 | let sut: CreateUserHandler 10 | let httpRequest: any 11 | 12 | beforeAll(() => { 13 | createUser = mock() 14 | createUser.execute.mockResolvedValue(1) 15 | }) 16 | 17 | beforeEach(() => { 18 | sut = new CreateUserHandler(createUser) 19 | httpRequest = mockUserInput() 20 | }) 21 | 22 | test('should ControllerStub extends Controller', async () => { 23 | expect(sut).toBeInstanceOf(Controller) 24 | }) 25 | 26 | test('should call CreateUserController with correct input', async () => { 27 | await sut.perform(httpRequest) 28 | 29 | expect(createUser.execute).toHaveBeenCalledWith({ name: httpRequest.name, email: httpRequest.email }) 30 | }) 31 | 32 | test('should return 204 on success', async () => { 33 | const httpResponse = await sut.perform(httpRequest) 34 | 35 | expect(httpResponse).toEqual({ 36 | statusCode: 200, 37 | data: 1 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /test/application/controllers/load-users-handler.spec.ts: -------------------------------------------------------------------------------- 1 | import { LoadUsersHandler, Controller } from '@/application/controllers' 2 | import { LoadUsersUC } from '@/domain/use-cases' 3 | import { User } from '@/domain/entities' 4 | import { mockUser } from '@/test/domain/mocks' 5 | 6 | import { mock, MockProxy } from 'jest-mock-extended' 7 | 8 | describe('LoadUsersController', () => { 9 | let loadUsers: MockProxy 10 | let sut: LoadUsersHandler 11 | let user: User 12 | 13 | beforeAll(() => { 14 | user = mockUser() 15 | loadUsers = mock() 16 | loadUsers.execute.mockResolvedValue([user]) 17 | }) 18 | 19 | beforeEach(() => { 20 | sut = new LoadUsersHandler(loadUsers) 21 | }) 22 | 23 | test('should ControllerStub extends Controller', async () => { 24 | expect(sut).toBeInstanceOf(Controller) 25 | }) 26 | 27 | test('should call LoadUsersController with correct input', async () => { 28 | await sut.perform() 29 | 30 | expect(loadUsers.execute).toHaveBeenCalled() 31 | expect(loadUsers.execute).toHaveBeenCalledTimes(1) 32 | }) 33 | 34 | test('should return 200 on success', async () => { 35 | const httpResponse = await sut.perform() 36 | 37 | expect(httpResponse).toEqual({ 38 | statusCode: 200, 39 | data: [user] 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/application/controllers/controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { ServerError } from '@/application/errors' 2 | import { Controller } from '@/application/controllers' 3 | import { HttpResponse } from '@/application/helpers' 4 | 5 | class ControllerStub extends Controller { 6 | result: HttpResponse = { 7 | statusCode: 200, 8 | data: 'any_data' 9 | } 10 | 11 | async perform (input: any): Promise { 12 | return this.result 13 | } 14 | } 15 | 16 | describe('Controller', () => { 17 | let sut: ControllerStub 18 | 19 | beforeEach(() => { 20 | sut = new ControllerStub() 21 | }) 22 | 23 | test('should ControllerStub extends Controller', async () => { 24 | expect(sut).toBeInstanceOf(Controller) 25 | }) 26 | 27 | test('should return 500 if perform throws', async () => { 28 | const error = new Error('perform_error') 29 | jest.spyOn(sut, 'perform').mockRejectedValueOnce(error) 30 | 31 | const httpResponse = await sut.handle({ data: 'any_data' }) 32 | 33 | expect(httpResponse).toEqual({ 34 | statusCode: 500, 35 | data: new ServerError(error) 36 | }) 37 | }) 38 | 39 | test('should return the result of perform', async () => { 40 | const httpResponse = await sut.handle('any_value') 41 | 42 | expect(httpResponse).toEqual(sut.result) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/main/routes/user.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserRoutes } from '@/main/routes' 2 | import { ControllerHandlersModule } from '@/application/controllers' 3 | import { PgTestHelper } from '@/test/infra/db/pg/helper' 4 | import { mockUserInput } from '@/test/application/mocks' 5 | 6 | import { Test, TestingModule } from '@nestjs/testing' 7 | import { getMockRes } from '@jest-mock/express' 8 | import { getConnection } from 'typeorm' 9 | import { Response } from 'express' 10 | 11 | describe('UserController', () => { 12 | let appController: UserRoutes 13 | let res: Response 14 | 15 | beforeAll(async () => { 16 | await PgTestHelper.connect() 17 | res = getMockRes().res 18 | }) 19 | 20 | beforeEach(async () => { 21 | PgTestHelper.restore() 22 | const app: TestingModule = await Test.createTestingModule({ 23 | imports: [ControllerHandlersModule], 24 | controllers: [UserRoutes] 25 | }).compile() 26 | 27 | appController = app.get(UserRoutes) 28 | }) 29 | 30 | afterAll(async () => await getConnection().close()) 31 | 32 | it('should return 1 on success', async () => { 33 | const params = mockUserInput() 34 | 35 | await appController.create(params, res) 36 | 37 | expect(res.status).toHaveBeenCalledWith(200) 38 | expect(res.json).toHaveBeenCalledWith(1) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /test/domain/use-cases/load-users.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserRepository } from '@/domain/contracts/repos' 2 | import { LoadUsersUC } from '@/domain/use-cases' 3 | import { mockUser } from '@/test/domain/mocks' 4 | import { User } from '@/domain/entities' 5 | 6 | import { MockProxy, mock } from 'jest-mock-extended' 7 | 8 | describe('CreateUser UseCase', () => { 9 | let userRepo: MockProxy 10 | let sut: LoadUsersUC 11 | let user: User 12 | 13 | beforeAll(() => { 14 | user = mockUser() 15 | userRepo = mock() 16 | userRepo.load.mockResolvedValue([user]) 17 | }) 18 | 19 | beforeEach(() => { 20 | sut = new LoadUsersUC(userRepo) 21 | }) 22 | 23 | test('should call UserRepo', async () => { 24 | await sut.execute() 25 | 26 | expect(userRepo.load).toHaveBeenCalled() 27 | expect(userRepo.load).toHaveBeenCalledTimes(1) 28 | }) 29 | 30 | test('should return an empty array if UserRepo returns an empty array', async () => { 31 | userRepo.load.mockResolvedValueOnce([]) 32 | 33 | const createAccount = await sut.execute() 34 | 35 | expect(createAccount).toEqual([]) 36 | }) 37 | 38 | test('should return 1 if UserRepo returns 1', async () => { 39 | const users = await sut.execute() 40 | 41 | expect(users).toEqual([{ id: user.id, email: user.email, name: user.name }]) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/domain/use-cases/create-user.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserRepository } from '@/domain/contracts/repos' 2 | import { CreateUserUC } from '@/domain/use-cases' 3 | import { CreateUserHandler } from '@/application/controllers' 4 | import { mockUserInput } from '@/test/application/mocks' 5 | 6 | import { MockProxy, mock } from 'jest-mock-extended' 7 | 8 | describe('CreateUser UseCase', () => { 9 | let userRepo: MockProxy 10 | let sut: CreateUserUC 11 | let userInput: CreateUserHandler.Input 12 | 13 | beforeAll(() => { 14 | userInput = mockUserInput() 15 | userRepo = mock() 16 | userRepo.create.mockResolvedValue(1) 17 | }) 18 | 19 | beforeEach(() => { 20 | sut = new CreateUserUC(userRepo) 21 | }) 22 | 23 | test('should call UserRepo with correct values', async () => { 24 | await sut.execute(userInput) 25 | 26 | expect(userRepo.create).toHaveBeenCalledWith(userInput) 27 | }) 28 | 29 | test('should return 0 if UserRepo doesnt create a user', async () => { 30 | userRepo.create.mockResolvedValueOnce(0) 31 | 32 | const createAccount = await sut.execute(userInput) 33 | 34 | expect(createAccount).toBe(0) 35 | }) 36 | 37 | test('should return 1 if UserRepo creates an user', async () => { 38 | const createAccount = await sut.execute(userInput) 39 | 40 | expect(createAccount).toBe(1) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/infra/db/pg/repos/user.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserPgRepo } from '@/infra/db/pg/repos' 2 | import { PhoneEntity, UserEntity } from '@/infra/db/pg/entities' 3 | import { PgTestHelper } from '@/test/infra/db/pg/helper' 4 | import { mockUserInput } from '@/test/application/mocks' 5 | 6 | import { getConnection, getRepository, Repository } from 'typeorm' 7 | 8 | describe('UserPgRepo', () => { 9 | let pgRepo: Repository 10 | let sut: UserPgRepo 11 | 12 | beforeAll(async () => { 13 | await PgTestHelper.connect([UserEntity, PhoneEntity]) 14 | pgRepo = getRepository(UserEntity) 15 | }) 16 | 17 | beforeEach(async () => { 18 | await PgTestHelper.restore() 19 | sut = new UserPgRepo() 20 | }) 21 | 22 | afterAll(async () => await getConnection().close()) 23 | 24 | describe('create()', () => { 25 | test('should create an user', async () => { 26 | const userId = await sut.create(mockUserInput()) 27 | const user = await pgRepo.findOne({ email: 'matheus.alxds@gmail.com' }) 28 | 29 | expect(userId).toBe(1) 30 | expect(user?.id).toBe(1) 31 | }) 32 | }) 33 | 34 | describe('load()', () => { 35 | test('should load all users', async () => { 36 | await pgRepo.insert({ email: 'any_email@mail.com', name: 'name_1' }) 37 | await pgRepo.insert({ email: 'any_email_2@mail.com', name: 'name_2' }) 38 | const users = await sut.load() 39 | 40 | expect(users).toHaveLength(2) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/main/routes/user-e2e.test.ts: -------------------------------------------------------------------------------- 1 | import { AppModule } from '@/main/factories/app.module' 2 | import { PhoneEntity, UserEntity } from '@/infra/db/pg/entities' 3 | import { PgTestHelper } from '@/test/infra/db/pg/helper' 4 | import { mockUserInput } from '@/test/application/mocks' 5 | 6 | import { Test, TestingModule } from '@nestjs/testing' 7 | import { INestApplication } from '@nestjs/common' 8 | import { getRepository, Repository } from 'typeorm' 9 | import * as request from 'supertest' 10 | 11 | describe('/users (e2e)', () => { 12 | let app: INestApplication 13 | let pgRepo: Repository 14 | 15 | beforeAll(async () => { 16 | await PgTestHelper.connect([UserEntity, PhoneEntity]) 17 | pgRepo = getRepository(UserEntity) 18 | }) 19 | 20 | beforeEach(async () => { 21 | PgTestHelper.restore() 22 | const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule] }).compile() 23 | app = moduleFixture.createNestApplication() 24 | await app.init() 25 | }) 26 | 27 | afterAll(async () => await PgTestHelper.disconnect()) 28 | 29 | describe('GET', () => { 30 | it('/', async () => { 31 | await pgRepo.insert([{ email: 'any_email', name: 'any_name' }, { email: 'any_email_2', name: 'any_name_2' }]) 32 | 33 | const { status, body } = await request(app.getHttpServer()).get('/users') 34 | 35 | expect(status).toBe(200) 36 | expect(body).toEqual([ 37 | { id: 1, email: 'any_email', name: 'any_name' }, 38 | { id: 2, email: 'any_email_2', name: 'any_name_2' } 39 | ]) 40 | }) 41 | }) 42 | 43 | describe('POST', () => { 44 | it('/', async () => { 45 | const { status, body } = await request(app.getHttpServer()) 46 | .post('/users') 47 | .send(mockUserInput()) 48 | 49 | expect(status).toBe(200) 50 | expect(body).toEqual(1) 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-js-project", 3 | "version": "0.0.1", 4 | "description": "NestJS with TDD and Clean Architecture", 5 | "author": "Matheus Alexandre da Silva", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint .", 16 | "lint:fix": "yarn lint -- --fix", 17 | "test": "jest --runInBand", 18 | "test:staged": "yarn test -- --findRelatedTests", 19 | "test:cov": "yarn test -- --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:unit": "yarn test -- --watch --config=jest-unit-config.js", 22 | "test:integration": "yarn test -- --watch -c=jest-integration-config.js", 23 | "jest:clear": "jest --clearCache", 24 | "up": "docker-compose up", 25 | "down": "docker-compose down", 26 | "create:migration": "typeorm migration:create -n", 27 | "gen:migration": "typeorm migration:generate -n", 28 | "run:migration": "typeorm migration:run", 29 | "rev:migration": "typeorm migration:revert", 30 | "prepare": "husky install" 31 | }, 32 | "dependencies": { 33 | "@nestjs/common": "^8.0.0", 34 | "@nestjs/config": "^1.2.0", 35 | "@nestjs/core": "^8.0.0", 36 | "@nestjs/platform-express": "^8.0.0", 37 | "@nestjs/typeorm": "^8.0.3", 38 | "jest-mock-extended": "^2.0.4", 39 | "reflect-metadata": "^0.1.13", 40 | "rimraf": "^3.0.2", 41 | "rxjs": "^7.2.0", 42 | "typeorm": "^0.2.44" 43 | }, 44 | "devDependencies": { 45 | "@jest-mock/express": "^1.4.5", 46 | "@nestjs/cli": "^8.0.0", 47 | "@nestjs/schematics": "^8.0.0", 48 | "@nestjs/testing": "^8.0.0", 49 | "@types/express": "^4.17.13", 50 | "@types/jest": "27.4.1", 51 | "@types/node": "^16.0.0", 52 | "@types/supertest": "^2.0.11", 53 | "@typescript-eslint/eslint-plugin": "^4.0.1", 54 | "eslint": "^7.12.1", 55 | "eslint-config-standard-with-typescript": "^21.0.1", 56 | "eslint-plugin-import": "^2.22.1", 57 | "eslint-plugin-node": "^11.1.0", 58 | "eslint-plugin-promise": "^5.0.0", 59 | "eslint-plugin-unused-imports": "^2.0.0", 60 | "git-commit-msg-linter": "^4.1.1", 61 | "husky": "^7.0.4", 62 | "jest": "^27.2.5", 63 | "lint-staged": "^12.3.7", 64 | "pg-mem": "^2.3.3", 65 | "source-map-support": "^0.5.20", 66 | "supertest": "^6.1.3", 67 | "ts-jest": "^27.0.3", 68 | "ts-loader": "^9.2.3", 69 | "ts-node": "^10.0.0", 70 | "tsconfig-paths": "^3.10.1", 71 | "typescript": "^4" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/main/adapters/nest-router.spec.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@/application/controllers' 2 | import { adaptNestRouter } from '@/main/adapters' 3 | 4 | import { Response } from 'express' 5 | import { MockProxy, mock } from 'jest-mock-extended' 6 | import { getMockRes } from '@jest-mock/express' 7 | 8 | describe('NestRouter', () => { 9 | let controller: MockProxy 10 | let sut: any 11 | let request: any 12 | let res: Response 13 | 14 | beforeAll(() => { 15 | request = { 16 | data: 'any_data' 17 | } 18 | res = getMockRes().res 19 | controller = mock() 20 | controller.handle.mockResolvedValue({ 21 | statusCode: 200, 22 | data: { data: 'any_data' } 23 | }) 24 | }) 25 | 26 | beforeEach(() => { sut = adaptNestRouter(controller) }) 27 | 28 | test('should call Controller with correct request', async () => { 29 | await sut(request, res) 30 | 31 | expect(controller.handle).toHaveBeenCalledWith({ data: 'any_data' }) 32 | expect(controller.handle).toHaveBeenCalledTimes(1) 33 | }) 34 | 35 | test('should return 200 and any data', async () => { 36 | await sut(request, res) 37 | 38 | expect(res.status).toHaveBeenCalledWith(200) 39 | expect(res.status).toHaveBeenCalledTimes(1) 40 | expect(res.json).toHaveBeenCalledWith({ data: 'any_data' }) 41 | expect(res.json).toHaveBeenCalledTimes(1) 42 | }) 43 | 44 | test('should return 204 and any data', async () => { 45 | controller.handle.mockResolvedValueOnce({ 46 | statusCode: 204, 47 | data: null 48 | }) 49 | 50 | await sut(request, res) 51 | 52 | expect(res.status).toHaveBeenCalledWith(204) 53 | expect(res.status).toHaveBeenCalledTimes(1) 54 | expect(res.json).toHaveBeenCalledWith(null) 55 | expect(res.json).toHaveBeenCalledTimes(1) 56 | }) 57 | 58 | test('should respond with 400 and valid error', async () => { 59 | controller.handle.mockResolvedValueOnce({ 60 | statusCode: 400, 61 | data: new Error('any_error') 62 | }) 63 | 64 | await sut(request, res) 65 | 66 | expect(res.status).toHaveBeenCalledWith(400) 67 | expect(res.status).toHaveBeenCalledTimes(1) 68 | expect(res.json).toHaveBeenCalledWith({ error: 'any_error' }) 69 | expect(res.json).toHaveBeenCalledTimes(1) 70 | }) 71 | 72 | test('should respond with 500 and valid error', async () => { 73 | controller.handle.mockResolvedValueOnce({ 74 | statusCode: 500, 75 | data: new Error('any_error') 76 | }) 77 | 78 | await sut(request, res) 79 | 80 | expect(res.status).toHaveBeenCalledWith(500) 81 | expect(res.status).toHaveBeenCalledTimes(1) 82 | expect(res.json).toHaveBeenCalledWith({ error: 'any_error' }) 83 | expect(res.json).toHaveBeenCalledTimes(1) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The Powerful NestJS 2 | 3 |

4 | Nest Logo 5 |

6 | 7 |

A progressive Node.js framework for building efficient and scalable server-side applications.

8 |

9 | NPM Version 10 | Package License 11 |

12 | 14 | 15 | ## Clean Architecture 16 | 17 | ![alt text](./public/clean-architecture.jpg "Clean Architecture") 18 | 19 | The project has been developed thinking about concepts of Clean Architecture, 20 | in a nutshell, I spread the functionalities in layers to better meet the responsibilities. 21 | 22 | Our `Domain` is responsible to keep **entities** and **use-cases**. `Application` 23 | layer is responsible for introducing controllers. `Infra` layer has the responsibility 24 | to become a gateway between our application and third packages, and last but not least, 25 | there's the `Main` layer, better known as a _"dirty layer"_, because the `Main` will 26 | connect each part of the project. 27 | 28 | ## TypeORM 29 | [TypeORM](https://typeorm.io/#/) is an ORM that can run in NodeJS, Browser, Cordova, 30 | PhoneGap, Ionic, React Native, NativeScript, Expo, and Electron platforms and can be 31 | used with TypeScript and JavaScript (ES5, ES6, ES7, ES8). Its goal is to always support 32 | the latest JavaScript features and provide additional features that help you to 33 | develop any kind of application that uses databases - from small applications with 34 | a few tables to large scale enterprise applications with multiple databases. 35 | 36 | ## Getting Started 37 | ### Installation 38 | 39 | ```bash 40 | $ npm install 41 | ``` 42 | 43 | ### Creating the database 44 | 45 | Before we start the application, it's necessary to create our database. 46 | 47 | It's very simple to create our migration files, as simple as you can think, you just need to run 48 | the following command: 49 | 50 | ```bash 51 | $ npm gen:migration 52 | ``` 53 | If you want to execute the migration file, you need to run the following command: 54 | 55 | ```bash 56 | $ npm run:migration 57 | ``` 58 | 59 | Sometimes, we need to revert the migration, so we need to run the following command: 60 | 61 | ```bash 62 | $ npm rev:migration 63 | ``` 64 | 65 | If you prefer to use the documentation for this, you can find it [here](https://orkhan.gitbook.io/typeorm/docs/migrations). 66 | 67 | ### Running the app 68 | 69 | After you create the database, it is possible to start the application with the following command: 70 | 71 | ```bash 72 | # development 73 | $ npm run start 74 | 75 | # watch mode 76 | $ npm run start:dev 77 | 78 | # production mode 79 | $ npm run start:prod 80 | ``` 81 | 82 | ### Test 83 | 84 | ```bash 85 | # unit tests 86 | $ npm run test 87 | 88 | # e2e tests 89 | $ npm run test:e2e 90 | 91 | # test coverage 92 | $ npm run test:cov 93 | ``` 94 | 95 | ## Support 96 | 97 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. 98 | If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 99 | 100 | ## License 101 | 102 | Nest is [MIT licensed](LICENSE). 103 | --------------------------------------------------------------------------------