├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src ├── app.module.ts ├── common │ ├── common.module.ts │ ├── decorators │ │ └── public.decorator.ts │ ├── exceptions │ │ └── custom-http.exception.ts │ ├── filters │ │ └── custom-exception.filter.ts │ ├── guards │ │ └── auth.guard.ts │ ├── interceptors │ │ ├── timeout.interceptor.ts │ │ └── wrap-data.interceptor.ts │ └── middlewares │ │ └── logger.middleware.ts ├── config │ ├── orm.config.prod.ts │ └── orm.config.ts ├── main.ts └── users │ ├── dtos │ ├── create-user.dto.ts │ ├── update-user.dto.ts │ └── user-response.dto.ts │ ├── notes.txt │ ├── pipes │ └── validation.pipe.ts │ ├── user.constants.ts │ ├── user.entity.ts │ ├── users.module.ts │ ├── users.service.ts │ └── usersController.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir : __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # env 2 | *.env 3 | .env 4 | .development.env 5 | .staging.env 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 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

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

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-fundamentals", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "NODE_ENV=development nest start --watch", 14 | "start:stag": "NODE_ENV=staging nest start --watch", 15 | "start:debug": "nest start --debug --watch", 16 | "start:prod": "node dist/main", 17 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 18 | "test": "jest", 19 | "test:watch": "jest --watch", 20 | "test:cov": "jest --coverage", 21 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 22 | "test:e2e": "jest --config ./test/jest-e2e.json" 23 | }, 24 | "dependencies": { 25 | "@nestjs/common": "^9.0.0", 26 | "@nestjs/config": "^2.3.1", 27 | "@nestjs/core": "^9.0.0", 28 | "@nestjs/mapped-types": "^1.2.0", 29 | "@nestjs/platform-express": "^9.0.0", 30 | "@nestjs/typeorm": "^9.0.1", 31 | "@types/uuid": "^9.0.0", 32 | "class-transformer": "^0.5.1", 33 | "class-validator": "^0.13.2", 34 | "reflect-metadata": "^0.1.13", 35 | "rimraf": "^3.0.2", 36 | "rxjs": "^7.2.0", 37 | "typeorm": "^0.3.12", 38 | "uuid": "^9.0.0" 39 | }, 40 | "devDependencies": { 41 | "@nestjs/cli": "^9.0.0", 42 | "@nestjs/schematics": "^9.0.0", 43 | "@nestjs/testing": "^9.0.0", 44 | "@types/express": "^4.17.13", 45 | "@types/jest": "28.1.8", 46 | "@types/node": "^16.0.0", 47 | "@types/supertest": "^2.0.11", 48 | "@typescript-eslint/eslint-plugin": "^5.0.0", 49 | "@typescript-eslint/parser": "^5.0.0", 50 | "eslint": "^8.0.1", 51 | "eslint-config-prettier": "^8.3.0", 52 | "eslint-plugin-prettier": "^4.0.0", 53 | "jest": "28.1.3", 54 | "prettier": "^2.3.2", 55 | "source-map-support": "^0.5.20", 56 | "supertest": "^6.1.3", 57 | "ts-jest": "28.0.8", 58 | "ts-loader": "^9.2.3", 59 | "ts-node": "^10.0.0", 60 | "tsconfig-paths": "4.1.0", 61 | "typescript": "~4.7.4" 62 | }, 63 | "jest": { 64 | "moduleFileExtensions": [ 65 | "js", 66 | "json", 67 | "ts" 68 | ], 69 | "rootDir": "src", 70 | "testRegex": ".*\\.spec\\.ts$", 71 | "transform": { 72 | "^.+\\.(t|j)s$": "ts-jest" 73 | }, 74 | "collectCoverageFrom": [ 75 | "**/*.(t|j)s" 76 | ], 77 | "coverageDirectory": "../coverage", 78 | "testEnvironment": "node" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, ValidationPipe } from '@nestjs/common'; 2 | import { UsersModule } from './users/users.module'; 3 | import { APP_PIPE } from '@nestjs/core'; 4 | 5 | import { CommonModule } from './common/common.module'; 6 | import { ConfigModule } from '@nestjs/config'; 7 | import { TypeOrmModule } from '@nestjs/typeorm'; 8 | import ormConfig from './config/orm.config'; 9 | import ormConfigProd from './config/orm.config.prod'; 10 | 11 | @Module({ 12 | imports: [ 13 | ConfigModule.forRoot({ 14 | envFilePath: 15 | process.env.NODE_ENV === 'development' 16 | ? '.development.env' 17 | : '.staging.env', 18 | isGlobal: true, 19 | expandVariables: true, 20 | 21 | // load: [ormConfig, ormConfigProd], 22 | // ignoreEnvFile: true, 23 | }), 24 | 25 | // TypeOrmModule.forRootAsync({ 26 | // useFactory: 27 | // process.env.NODE_ENV === 'development' ? ormConfig : ormConfigProd, 28 | // }), 29 | 30 | UsersModule, 31 | CommonModule, 32 | ], 33 | providers: [{ provide: APP_PIPE, useClass: ValidationPipe }], 34 | }) 35 | export class AppModule {} 36 | -------------------------------------------------------------------------------- /src/common/common.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { APP_GUARD } from '@nestjs/core'; 3 | import { AuthGuard } from './guards/auth.guard'; 4 | 5 | @Module({ 6 | providers: [{ provide: APP_GUARD, useClass: AuthGuard }], 7 | }) 8 | export class CommonModule {} 9 | -------------------------------------------------------------------------------- /src/common/decorators/public.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const IS_PUBLIC_KEY = 'Is_Public'; 4 | 5 | export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); 6 | -------------------------------------------------------------------------------- /src/common/exceptions/custom-http.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | export class CustomHttpException extends HttpException { 4 | constructor() { 5 | super('CustomHttpException', HttpStatus.AMBIGUOUS); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/common/filters/custom-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpException, 6 | } from '@nestjs/common'; 7 | import { Request, Response } from 'express'; 8 | 9 | @Catch(HttpException) 10 | export class CustomExceptionFilter 11 | implements ExceptionFilter 12 | { 13 | catch(exception: T, host: ArgumentsHost) { 14 | const ctx = host.switchToHttp(); 15 | const request = ctx.getRequest(); 16 | const response = ctx.getResponse(); 17 | 18 | const status = exception.getStatus(); 19 | 20 | // prepare error 21 | const exceptionResponse = exception.getResponse(); 22 | 23 | // translate logic use case 24 | const error = 25 | typeof response === 'string' 26 | ? { message: exceptionResponse } 27 | : (exceptionResponse as object); 28 | 29 | // use sentry to send errors 30 | // sentry.log(error) 31 | 32 | response.status(status).json({ 33 | ...error, 34 | statusCode: status, 35 | timestamp: new Date().toISOString(), 36 | path: request.url, 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | Injectable, 5 | UnauthorizedException, 6 | } from '@nestjs/common'; 7 | import { Observable } from 'rxjs'; 8 | import { Request } from 'express'; 9 | import { Reflector } from '@nestjs/core'; 10 | import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; 11 | 12 | @Injectable() 13 | export class AuthGuard implements CanActivate { 14 | constructor(private reflector: Reflector) {} 15 | 16 | canActivate( 17 | context: ExecutionContext, 18 | ): boolean | Promise | Observable { 19 | const jwt = 20 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; 21 | 22 | // handle access status 23 | const isPublic = this.reflector.get( 24 | IS_PUBLIC_KEY, 25 | context.getHandler(), 26 | ); 27 | if (isPublic) { 28 | return true; 29 | } 30 | 31 | // verify token if verified return true, if not return false 32 | const ctx = context.switchToHttp(); 33 | const req = ctx.getRequest(); 34 | 35 | const token = req.header('Authorization') 36 | ? req.header('Authorization').split(' ')[1] 37 | : ''; 38 | 39 | if (token !== jwt) { 40 | throw new UnauthorizedException('invalid token..'); 41 | } 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/common/interceptors/timeout.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CallHandler, 3 | ExecutionContext, 4 | Injectable, 5 | NestInterceptor, 6 | RequestTimeoutException, 7 | } from '@nestjs/common'; 8 | import { 9 | catchError, 10 | Observable, 11 | throwError, 12 | timeout, 13 | TimeoutError, 14 | } from 'rxjs'; 15 | import { Request } from 'express'; 16 | 17 | @Injectable() 18 | export class TimeoutInterceptor implements NestInterceptor { 19 | intercept(context: ExecutionContext, next: CallHandler): Observable { 20 | // Logic on current request, before method execution 21 | const ctx = context.switchToHttp(); 22 | const request = ctx.getRequest(); 23 | 24 | request.body = { ...request.body, username: 'Test user' }; 25 | 26 | return next.handle().pipe( 27 | timeout(3000), 28 | catchError((err) => { 29 | if (err instanceof TimeoutError) { 30 | return throwError(() => new RequestTimeoutException()); 31 | } 32 | return err; 33 | }), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/common/interceptors/wrap-data.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CallHandler, 3 | ExecutionContext, 4 | Injectable, 5 | NestInterceptor, 6 | } from '@nestjs/common'; 7 | import { map, Observable } from 'rxjs'; 8 | import { UserService } from '../../users/users.service'; 9 | 10 | @Injectable() 11 | export class WrapDataInterceptor implements NestInterceptor { 12 | // constructor(private readonly userService: UserService) {} 13 | intercept(context: ExecutionContext, next: CallHandler): Observable { 14 | // logic: Intercept request 15 | // console.log('Before, Request intercepting.....'); 16 | // console.log('Interceptor called....'); 17 | 18 | return next.handle().pipe( 19 | map((data) => { 20 | // logic: Intercept response 21 | // console.log('After, Response intercepting.....', data); 22 | return { response: data }; 23 | }), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/common/middlewares/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { NextFunction, Request, Response } from 'express'; 3 | import { UserService } from '../../users/users.service'; 4 | 5 | @Injectable() 6 | export class LoggerMiddleware implements NestMiddleware { 7 | use(req: Request, res: Response, next: NextFunction) { 8 | // any logic 9 | console.log('Logger middleware....'); 10 | 11 | next(); 12 | } 13 | } 14 | 15 | // export const loggerMiddleware = ( 16 | // req: Request, 17 | // res: Response, 18 | // next: NextFunction, 19 | // ) => { 20 | // // any logic 21 | // console.log('function Logger middleware....'); 22 | // next(); 23 | // }; 24 | -------------------------------------------------------------------------------- /src/config/orm.config.prod.ts: -------------------------------------------------------------------------------- 1 | import { TypeOrmModuleOptions } from '@nestjs/typeorm'; 2 | 3 | export default (): TypeOrmModuleOptions => ({ 4 | type: 'postgres', 5 | host: process.env.DATABASE_HOST, 6 | port: parseInt(process.env.DATABASE_PORT), 7 | username: process.env.DATABASE_USERNAME, 8 | password: process.env.DATABASE_PASSWORD, 9 | database: process.env.DATABASE_NAME, 10 | entities: [__dirname + '/**/*.entity{.ts,.js}'], 11 | synchronize: false, 12 | }); 13 | -------------------------------------------------------------------------------- /src/config/orm.config.ts: -------------------------------------------------------------------------------- 1 | import { TypeOrmModuleOptions } from '@nestjs/typeorm'; 2 | 3 | export default (): TypeOrmModuleOptions => ({ 4 | type: 'postgres', 5 | host: process.env.DATABASE_HOST, 6 | port: parseInt(process.env.DATABASE_PORT), 7 | username: process.env.DATABASE_USERNAME, 8 | password: process.env.DATABASE_PASSWORD, 9 | database: process.env.DATABASE_NAME, 10 | entities: [__dirname + '/**/*.entity{.ts,.js}'], 11 | synchronize: true, 12 | }); 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { NestExpressApplication } from '@nestjs/platform-express'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | await app.listen(3000); 8 | } 9 | bootstrap(); 10 | -------------------------------------------------------------------------------- /src/users/dtos/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, Length } from 'class-validator'; 2 | 3 | export class CreateUserDto { 4 | @IsString() 5 | @Length(3, 20) 6 | readonly username: string; 7 | 8 | @IsEmail({}, { message: 'incorrect email' }) 9 | readonly email: string; 10 | 11 | @IsString() 12 | readonly country: string; 13 | 14 | @IsString() 15 | readonly password: string; 16 | } 17 | -------------------------------------------------------------------------------- /src/users/dtos/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from "@nestjs/mapped-types"; 2 | import { CreateUserDto } from "./create-user.dto"; 3 | 4 | export class UpdateUserDto extends PartialType(CreateUserDto){} 5 | 6 | -------------------------------------------------------------------------------- /src/users/dtos/user-response.dto.ts: -------------------------------------------------------------------------------- 1 | import { Exclude, Expose } from 'class-transformer'; 2 | 3 | export class UserResponseDto { 4 | id: string; 5 | username: string; 6 | email: string; 7 | 8 | @Expose({ name: 'Country' }) 9 | country: string; 10 | 11 | // @Expose({ name: 'Address' }) 12 | // getCountry(): string { 13 | // return this.country; 14 | // } 15 | 16 | @Exclude() 17 | password: string; 18 | 19 | constructor(partial: Partial) { 20 | Object.assign(this, partial); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/users/notes.txt: -------------------------------------------------------------------------------- 1 | DI => Dependency Injection Design Pattern 2 | 3 | => UserController => U, AuthService => UserRepository, AuthRepository, 4 | ProductRepository, OrderRepository 5 | 6 | ==> Instance UserController 7 | const userController = new UserController(new UserService(new UserRepository(), new AuthService(new AuthRepository()))) 8 | 9 | => IoC => Inversion of Control => Means 10 | => We don't create instance of class manually, we let the framework create it for us. 11 | 12 | => DI and IoC => manage dependencies for us. 13 | 14 | ------------------------------------------------------------------------------------------------ 15 | ==> DI Container: 16 | => We can use DI Container to manage dependencies for us. 17 | => UserService => UserRepository 18 | => AuthService => AuthRepository 19 | 20 | => instance UserController 21 | -------------------------------------------------------------------------------- /src/users/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class CustomValidationPipe implements PipeTransform { 5 | transform(value: any, metadata: ArgumentMetadata) { 6 | // Write your own logic here (Transformation or validation) 7 | console.log('CustomValidationPipe', value); 8 | return 'DEV' + value; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/users/user.constants.ts: -------------------------------------------------------------------------------- 1 | export const APP_NAME = 'APP_NAME'; 2 | export const USER_HABITS = 'USER_HABITS'; 3 | -------------------------------------------------------------------------------- /src/users/user.entity.ts: -------------------------------------------------------------------------------- 1 | export class UserEntity { 2 | id: string; 3 | username: string; 4 | email: string; 5 | password: string; 6 | country: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Module } from '@nestjs/common'; 2 | import { UsersController } from './usersController'; 3 | import { UserService } from './users.service'; 4 | import { APP_NAME, USER_HABITS } from './user.constants'; 5 | import { ConfigModule } from '@nestjs/config'; 6 | 7 | class MockUserService { 8 | findUsers() { 9 | return ['user1', 'user2']; 10 | } 11 | } 12 | abstract class ConfigService {} 13 | class DevelopmentConfigService extends ConfigService {} 14 | class ProductionConfigService extends ConfigService {} 15 | 16 | @Injectable() 17 | class UserHabitsFactory { 18 | getHabits() { 19 | return ['eat', 'sleep', 'code']; 20 | } 21 | } 22 | 23 | @Injectable() 24 | class LoggerService { 25 | constructor(private readonly userService: UserService) {} 26 | // logic 27 | } 28 | 29 | // Alias provider 30 | const loggerServiceAliasProvider = { 31 | provide: 'LoggerServiceAlias', 32 | useExisting: LoggerService, 33 | }; 34 | 35 | @Injectable() 36 | class DatabaseConnection { 37 | async connectToDB(): Promise { 38 | return await Promise.resolve('connectToDB successfully'); 39 | } 40 | } 41 | 42 | @Module({ 43 | controllers: [UsersController], 44 | providers: [ 45 | // Standard provider 46 | UserService, 47 | UserHabitsFactory, 48 | LoggerService, 49 | loggerServiceAliasProvider, 50 | DatabaseConnection, 51 | // Custom provider 52 | // value based provider 53 | { 54 | provide: APP_NAME, 55 | useValue: 'Nest Demo API', 56 | }, 57 | // class based provider 58 | { 59 | provide: ConfigService, 60 | useClass: 61 | process.env.NODE_ENV === 'development' 62 | ? DevelopmentConfigService 63 | : ProductionConfigService, 64 | }, 65 | 66 | // factory based provider + async factory based provider 67 | { 68 | provide: USER_HABITS, 69 | useFactory: async ( 70 | userHabits: UserHabitsFactory, 71 | dbConnection: DatabaseConnection, 72 | ) => { 73 | // Connect to db 74 | const dbStatus = await dbConnection.connectToDB(); 75 | // console.log(dbStatus); 76 | 77 | return userHabits.getHabits(); 78 | }, 79 | inject: [UserHabitsFactory, DatabaseConnection], 80 | }, 81 | ], 82 | exports: [USER_HABITS], 83 | }) 84 | export class UsersModule {} 85 | -------------------------------------------------------------------------------- /src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { UserEntity } from './user.entity'; 3 | import { UpdateUserDto } from './dtos/update-user.dto'; 4 | import { CreateUserDto } from './dtos/create-user.dto'; 5 | import { v4 as uuid } from 'uuid'; 6 | import { UserResponseDto } from './dtos/user-response.dto'; 7 | 8 | @Injectable() 9 | export class UserService { 10 | constructor() {} 11 | 12 | private users: UserEntity[] = []; 13 | 14 | findUsers(): UserEntity[] { 15 | return this.users; 16 | } 17 | 18 | findUserById(id: string): UserResponseDto { 19 | const user = this.users.find((user) => user.id === id); 20 | if (!user) { 21 | throw new NotFoundException(`Not found user ${id}`); 22 | } 23 | return new UserResponseDto(user); 24 | } 25 | 26 | createUser(createUserDto: CreateUserDto): UserResponseDto { 27 | const newUser: UserEntity = { 28 | ...createUserDto, 29 | id: uuid(), 30 | }; 31 | this.users.push(newUser); 32 | 33 | return new UserResponseDto(newUser); 34 | } 35 | 36 | updateUser(id: string, updateUserDto: UpdateUserDto): UserEntity { 37 | // 1) find the element index that we want to update 38 | const index = this.users.findIndex((user) => user.id === id); 39 | // 2) update the element 40 | this.users[index] = { ...this.users[index], ...updateUserDto }; 41 | 42 | return this.users[index]; 43 | } 44 | 45 | deleteUser(id: string): void { 46 | this.users = this.users.filter((user) => user.id !== id); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/users/usersController.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | HttpCode, 7 | HttpStatus, 8 | Logger, 9 | Param, 10 | ParseUUIDPipe, 11 | Patch, 12 | Post, 13 | Req, 14 | } from '@nestjs/common'; 15 | import { CreateUserDto } from './dtos/create-user.dto'; 16 | import { UpdateUserDto } from './dtos/update-user.dto'; 17 | import { UserEntity } from './user.entity'; 18 | import { v4 as uuid } from 'uuid'; 19 | import { UserService } from './users.service'; 20 | import { retry } from 'rxjs'; 21 | import { UserResponseDto } from './dtos/user-response.dto'; 22 | import { Request } from 'express'; 23 | import { Public } from '../common/decorators/public.decorator'; 24 | import { ConfigService } from '@nestjs/config'; 25 | 26 | interface EnvironmentVariables { 27 | PORT: number; 28 | EMAIL: string; 29 | } 30 | 31 | @Controller('users') 32 | export class UsersController { 33 | logger: Logger = new Logger(UsersController.name); 34 | 35 | constructor( 36 | private readonly configService: ConfigService, 37 | private readonly userService: UserService, 38 | ) { 39 | // console.log(this.configService.get('EMAIL', { infer: true })); 40 | } 41 | 42 | @Public() 43 | @Get() 44 | async find(@Req() req: Request): Promise { 45 | this.logger.log('Getting all users'); 46 | 47 | const users = await this.userService.findUsers(); 48 | 49 | this.logger.debug(`Found ${users.length} users`); 50 | console.log(`Found ${users.length} users`); 51 | // any logic depend on users length 52 | return users; 53 | } 54 | 55 | @Public() 56 | @Get(':id') 57 | findOne( 58 | @Param('id', ParseUUIDPipe) 59 | id: string, 60 | ): UserResponseDto { 61 | return this.userService.findUserById(id); 62 | } 63 | 64 | @Post() 65 | create( 66 | @Body() 67 | createUserDto: CreateUserDto, 68 | ): UserResponseDto { 69 | return this.userService.createUser(createUserDto); 70 | } 71 | 72 | @Patch(':id') 73 | update( 74 | @Param('id', ParseUUIDPipe) id: string, 75 | @Body() 76 | updateUserDto: UpdateUserDto, 77 | ) { 78 | return this.userService.updateUser(id, updateUserDto); 79 | } 80 | 81 | @Delete(':id') 82 | @HttpCode(HttpStatus.NO_CONTENT) 83 | remove(@Param('id', ParseUUIDPipe) id: string) { 84 | this.userService.deleteUser(id); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /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": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | --------------------------------------------------------------------------------