├── .gitignore ├── README.md ├── demo ├── easy-post │ ├── README.md │ ├── nodemon-debug.json │ ├── nodemon.json │ ├── ormconfig.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── common │ │ │ ├── common.module.ts │ │ │ ├── decorators │ │ │ │ └── roles.decorator.ts │ │ │ ├── interfaces │ │ │ │ └── result.interface.ts │ │ │ └── utils │ │ │ │ └── crypto.util.ts │ │ ├── core │ │ │ ├── auth │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.ts │ │ │ │ └── auth.strategy.ts │ │ │ ├── guards │ │ │ │ └── roles.guard.ts │ │ │ └── interceptors │ │ │ │ └── errors.interceptor.ts │ │ ├── feature │ │ │ ├── post │ │ │ │ ├── post.controller.ts │ │ │ │ ├── post.entity.ts │ │ │ │ ├── post.module.ts │ │ │ │ └── post.service.ts │ │ │ └── user │ │ │ │ ├── user.controller.ts │ │ │ │ ├── user.entity.ts │ │ │ │ ├── user.module.ts │ │ │ │ └── user.service.ts │ │ └── main.ts │ ├── tsconfig.json │ ├── tslint.json │ └── yarn.lock ├── graphql-api │ ├── README.md │ ├── images │ │ ├── createCat.png │ │ ├── deleteCat.png │ │ ├── findCats.png │ │ ├── findOneCat.png │ │ └── updateCat.png │ ├── nodemon-debug.json │ ├── nodemon.json │ ├── ormconfig.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── app.resolver.ts │ │ ├── app.service.ts │ │ ├── app.types.graphql │ │ ├── cat │ │ │ ├── cat.entity.ts │ │ │ ├── cat.module.ts │ │ │ ├── cat.resolver.ts │ │ │ ├── cat.service.ts │ │ │ └── cat.types.graphql │ │ ├── common │ │ │ ├── errors.interceptor.ts │ │ │ └── result.interface.ts │ │ ├── main.ts │ │ └── pub-sub.provider.ts │ ├── tsconfig.json │ ├── tslint.json │ └── yarn.lock └── rest-api │ ├── README.md │ ├── nodemon-debug.json │ ├── nodemon.json │ ├── ormconfig.json │ ├── package.json │ ├── src │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── cat │ │ ├── cat.controller.ts │ │ ├── cat.entity.ts │ │ ├── cat.module.ts │ │ └── cat.service.ts │ ├── common │ │ ├── errors.interceptor.ts │ │ └── result.interface.ts │ └── main.ts │ ├── tsconfig.json │ ├── tslint.json │ └── yarn.lock ├── docs ├── controller.md ├── exception-filter.md ├── middleware.md ├── module.md ├── nest-factory.md └── provider.md ├── issues └── typeorm │ └── pagination │ ├── README.md │ ├── images │ ├── post.png │ └── user.png │ ├── package.json │ ├── src │ ├── pagination.test.ts │ ├── post.entity.ts │ └── user.entity.ts │ ├── tsconfig.json │ └── yarn.lock └── renovate.json /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | 3 | # Created by https://www.gitignore.io/api/linux,visualstudiocode,node,webstorm 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Runtime data 29 | pids 30 | *.pid 31 | *.seed 32 | *.pid.lock 33 | 34 | # Directory for instrumented libs generated by jscoverage/JSCover 35 | lib-cov 36 | 37 | # Coverage directory used by tools like istanbul 38 | coverage 39 | 40 | # nyc test coverage 41 | .nyc_output 42 | 43 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 44 | .grunt 45 | 46 | # Bower dependency directory (https://bower.io/) 47 | bower_components 48 | 49 | # node-waf configuration 50 | .lock-wscript 51 | 52 | # Compiled binary addons (https://nodejs.org/api/addons.html) 53 | build/Release 54 | 55 | # Dependency directories 56 | node_modules/ 57 | jspm_packages/ 58 | 59 | # TypeScript v1 declaration files 60 | typings/ 61 | 62 | # Optional npm cache directory 63 | .npm 64 | 65 | # Optional eslint cache 66 | .eslintcache 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # next.js build output 84 | .next 85 | 86 | # nuxt.js build output 87 | .nuxt 88 | 89 | # vuepress build output 90 | .vuepress/dist 91 | 92 | # Serverless directories 93 | .serverless 94 | 95 | ### VisualStudioCode ### 96 | .vscode/* 97 | !.vscode/settings.json 98 | !.vscode/tasks.json 99 | !.vscode/launch.json 100 | !.vscode/extensions.json 101 | 102 | ### WebStorm ### 103 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 104 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 105 | 106 | # User-specific stuff 107 | .idea/**/workspace.xml 108 | .idea/**/tasks.xml 109 | .idea/**/usage.statistics.xml 110 | .idea/**/dictionaries 111 | .idea/**/shelf 112 | 113 | # Sensitive or high-churn files 114 | .idea/**/dataSources/ 115 | .idea/**/dataSources.ids 116 | .idea/**/dataSources.local.xml 117 | .idea/**/sqlDataSources.xml 118 | .idea/**/dynamic.xml 119 | .idea/**/uiDesigner.xml 120 | .idea/**/dbnavigator.xml 121 | 122 | # Gradle 123 | .idea/**/gradle.xml 124 | .idea/**/libraries 125 | 126 | # Gradle and Maven with auto-import 127 | # When using Gradle or Maven with auto-import, you should exclude module files, 128 | # since they will be recreated, and may cause churn. Uncomment if using 129 | # auto-import. 130 | # .idea/modules.xml 131 | # .idea/*.iml 132 | # .idea/modules 133 | 134 | # CMake 135 | cmake-build-*/ 136 | 137 | # Mongo Explorer plugin 138 | .idea/**/mongoSettings.xml 139 | 140 | # File-based project format 141 | *.iws 142 | 143 | # IntelliJ 144 | out/ 145 | 146 | # mpeltonen/sbt-idea plugin 147 | .idea_modules/ 148 | 149 | # JIRA plugin 150 | atlassian-ide-plugin.xml 151 | 152 | # Cursive Clojure plugin 153 | .idea/replstate.xml 154 | 155 | # Crashlytics plugin (for Android Studio and IntelliJ) 156 | com_crashlytics_export_strings.xml 157 | crashlytics.properties 158 | crashlytics-build.properties 159 | fabric.properties 160 | 161 | # Editor-based Rest Client 162 | .idea/httpRequests 163 | 164 | ### WebStorm Patch ### 165 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 166 | 167 | # *.iml 168 | # modules.xml 169 | # .idea/misc.xml 170 | # *.ipr 171 | 172 | # Sonarlint plugin 173 | .idea/sonarlint 174 | 175 | 176 | # End of https://www.gitignore.io/api/linux,visualstudiocode,node,webstorm 177 | 178 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nest.js 教程 2 | 3 | 如果喜欢本教程请点击右上角的 star 🌟,想订阅本教程请点击右上角 watch 👀 4 | 5 | 如有不解之惑,或是想要更多的详细教程,可以在 [New Issues](https://github.com/dzzzzzy/Nestjs-Learning/issues/new) 中写下你的问题或需求。 6 | 7 | ## 框架介绍 8 | 9 | Nest 是构建高效,可扩展的 Node.js Web 应用程序的框架。它使用现代的 JavaScript 或 TypeScript(保留与纯 JavaScript 的兼容性),并结合 OOP(面向对象编程),FP(函数式编程)和 FRP(函数响应式编程)的元素。 10 | 11 | 在底层,Nest 使用了 [Express](https://github.com/expressjs/express),但也提供了与其他各种库的兼容,例如 [Fastify](https://github.com/fastify/fastify),可以方便地使用各种可用的第三方插件。 12 | 13 | ### 主要特性 14 | 15 | - 使用 Typescript,强类型语言、类型推断机制、编译期类型检查等,为后端开发和维护提供了很好的支持 16 | - 模块化开发,让应用程序更容易分层,提供了易于使用的模块化管理机制 17 | - 内置 IOC 容器,大量使用依赖注入,开发更便捷、更高效 18 | - 轻松编写 AOP 代码,面向切面编程,轻松实现日志、拦截器、过滤器等功能 19 | - 支持 TypeORM,最好的 Typescript ORM 框架,轻松编写 DAO 层的各类逻辑 20 | - 轻松构建 MVC、API、websocket、微服务等系统 21 | - ...... 22 | 23 | ### 框架 Github 地址: [Nest](https://github.com/nestjs/nest) 🎁 24 | 25 | ## 框架文档 26 | 27 | [官方文档](https://docs.nestjs.com) [中文文档](https://docs.nestjs.cn) 28 | 29 | ## 相关技术文档 30 | 31 | - [NodeJS](https://nodejs.org) 32 | - [v12文档(最新)](https://nodejs.org/dist/latest-v12.x/docs/api/) 33 | - [Typescript](https://www.typescriptlang.org) 34 | - 📚 [官方文档](https://www.typescriptlang.org/docs/home.html) 35 | - 📚 [中文文档](https://www.tslang.cn/docs/home.html) 36 | - [GraphQL](https://github.com/graphql/graphql-js) 37 | - 📚 [官方文档](https://graphql.org) 38 | - 📚 [中文文档](https://graphql.cn) 39 | - 🏫 [GraphQL的全栈教程](https://www.howtographql.com/) 40 | - GraphQL IDE [graphql-playground](https://github.com/prisma/graphql-playground) 41 | - [TypeORM](https://github.com/typeorm/typeorm) 42 | - 📚 [官方文档](http://typeorm.io) 43 | - ...... 44 | 45 | ## Nest 学习手册 46 | 47 | > 由于官方文档更新较为频繁,推荐大家尽可能的查阅[官方文档](https://docs.nestjs.com)进行学习 48 | 49 | - 基础功能 50 | - [Controller](./docs/controller.md) 控制器 51 | - [Provider](./docs/provider.md) 提供者 52 | - [Module](./docs/module.md) 模块(核心依赖注入思想) 53 | - [NestFactory](./docs/nest-factory.md) 创建 Nest 应用的工厂类 54 | - 高级功能 55 | - [Middleware](./docs/middleware.md) 中间件 56 | - [Exception Filter](./docs/exception-filter.md) 异常过滤器 57 | - [Pipe](https://docs.nestjs.com/pipes) 管道 58 | - [Guard](https://docs.nestjs.com/guards) 守卫 59 | - [Interceptor](https://docs.nestjs.com/interceptors) 拦截器 60 | 61 | ## Demo 62 | 63 | - [CRUD Restful API Demo](./demo/rest-api/README.md) Restful 风格的增删改查示例项目。 64 | - [GraphQL API Demo](./demo/graphql-api/README.md) GraphQL 风格的增删改查示例项目,包含简单的 GraphQL 订阅功能。 65 | - [easy-post](./demo/easy-post/README.md) 便利贴,包含用户登录注册,用户授权、用户认证、帖子管理、用户管理等功能。 66 | 67 | ## QQ 交流群 68 | 69 | 489719517 70 | 71 | ## nestjs 框架中国开发者社区 72 | 73 | 社区地址:[github](https://github.com/nest-cn-community) 74 | 75 | ## TypeORM 问题汇总 76 | 77 | - [分页查询问题](./issues/typeorm/pagination/README.md) 78 | 79 | ***TODO*** 80 | 81 | - [ ] 相关技术学习总结 82 | - [ ] 开发技巧总结 83 | - [x] 常见问题汇总 84 | - [x] demo 85 | -------------------------------------------------------------------------------- /demo/easy-post/README.md: -------------------------------------------------------------------------------- 1 | # Easy-Post(便利贴) 2 | 3 | 使用 Nest 构建,一个简单的用户发帖系统。 4 | 5 | ## 功能 6 | 7 | - [x] 注册 8 | - [x] 登录 9 | - [x] 用户授权(jwt) 10 | - [x] 用户认证(简单的RBAC) 11 | - [x] 用户管理 12 | - [x] 帖子管理 13 | 14 | ## 项目结构 15 | 16 | - common 用于存放公共的 interface、decorators 17 | - core 用于存放核心的 auth、guards、interceprots 18 | - feature 用于存放系统业务模块,用户模块、帖子模块 19 | - shared 用于存放系统分享模块,分享模块可以被其他任意业务模块导入,并使用其分享出的 provider 20 | 21 | ## 使用说明 22 | 23 | ```bash 24 | # 安装依赖 25 | $ yarn install 26 | 27 | # 创建 test 数据库 28 | 29 | # 启动程序 30 | $ yarn run start 31 | ``` -------------------------------------------------------------------------------- /demo/easy-post/nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "exec": "node --inspect-brk -r ts-node/register src/main.ts" 10 | } -------------------------------------------------------------------------------- /demo/easy-post/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 10 | } -------------------------------------------------------------------------------- /demo/easy-post/ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": 5432, 5 | "username": "postgres", 6 | "password": "123456", 7 | "database": "test", 8 | "entities": [ 9 | "src/**/**.entity{.ts,.js}" 10 | ], 11 | "synchronize": true, 12 | "logging": true 13 | } -------------------------------------------------------------------------------- /demo/easy-post/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easy-post", 3 | "version": "1.0.0", 4 | "description": "Easy post system.", 5 | "main": "main.ts", 6 | "scripts": { 7 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 8 | "start:watch": "nodemon", 9 | "debug": "node --inspect-brk -r ts-node/register src/main.ts", 10 | "debug:watch": "nodemon --config nodemon-debug.json", 11 | "check": "tslint -p tsconfig.json -c tslint.json", 12 | "fix": "tslint -p tsconfig.json -c tslint.json --fix" 13 | }, 14 | "author": "dzzzzzy", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@nestjs/common": "^6.5.3", 18 | "@nestjs/core": "^6.5.3", 19 | "@nestjs/jwt": "^6.1.1", 20 | "@nestjs/passport": "^7.0.0", 21 | "@nestjs/platform-express": "^6.5.3", 22 | "@nestjs/typeorm": "^7.0.0", 23 | "passport": "^0.4.0", 24 | "passport-jwt": "^4.0.0", 25 | "pg": "^7.9.0", 26 | "reflect-metadata": "^0.1.13", 27 | "rxjs": "^6.5.2", 28 | "typeorm": "^0.2.15", 29 | "typescript": "^3.5.3" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "^11.9.4", 33 | "@types/passport-jwt": "^3.0.1", 34 | "nodemon": "^1.19.1", 35 | "ts-node": "^8.3.0", 36 | "tsconfig-paths": "^3.8.0", 37 | "tslint": "^5.18.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/easy-post/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { APP_INTERCEPTOR } from '@nestjs/core'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | 5 | import { ErrorsInterceptor } from './core/interceptors/errors.interceptor'; 6 | import { PostModule } from './feature/post/post.module'; 7 | import { UserModule } from './feature/user/user.module'; 8 | 9 | @Module({ 10 | imports: [ 11 | TypeOrmModule.forRoot(), // 建立 typeorm 与数据库的连接 12 | UserModule, 13 | PostModule 14 | ], 15 | providers: [ 16 | { 17 | provide: APP_INTERCEPTOR, // 全局拦截器,这里使用全局异常拦截器改写异常消息结构 18 | useClass: ErrorsInterceptor 19 | } 20 | ] 21 | }) 22 | export class AppModule { } -------------------------------------------------------------------------------- /demo/easy-post/src/common/common.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { CryptoUtil } from './utils/crypto.util'; 4 | 5 | /** 6 | * 公共模块,向 nest 容器提供单例的公共模块,其他模块使用公共模块导出的 provider 时,只需导入 CommonModule 7 | */ 8 | @Module({ 9 | providers: [CryptoUtil], 10 | exports: [CryptoUtil] 11 | }) 12 | export class CommonModule { } -------------------------------------------------------------------------------- /demo/easy-post/src/common/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const Roles = (...roles: string[]) => SetMetadata('roles', roles); -------------------------------------------------------------------------------- /demo/easy-post/src/common/interfaces/result.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Result { 2 | code: number; 3 | message: string; 4 | data?: any; 5 | } 6 | -------------------------------------------------------------------------------- /demo/easy-post/src/common/utils/crypto.util.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { createHash } from 'crypto'; 3 | 4 | @Injectable() 5 | export class CryptoUtil { 6 | 7 | /** 8 | * 加密登录密码 9 | * 10 | * @param password 登录密码 11 | */ 12 | encryptPassword(password: string): string { 13 | return createHash('sha256').update(password).digest('hex'); 14 | } 15 | 16 | /** 17 | * 检查登录密码是否正确 18 | * 19 | * @param password 登录密码 20 | * @param encryptedPassword 加密后的密码 21 | */ 22 | checkPassword(password: string, encryptedPassword): boolean { 23 | const currentPass = this.encryptPassword(password); 24 | if (currentPass === encryptedPassword) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | } -------------------------------------------------------------------------------- /demo/easy-post/src/core/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { JwtModule } from '@nestjs/jwt'; 3 | 4 | import { UserModule } from '../../feature/user/user.module'; 5 | import { AuthService } from './auth.service'; 6 | import { AuthStrategy } from './auth.strategy'; 7 | 8 | @Module({ 9 | imports: [ 10 | JwtModule.register({ // 向 nest 容器注册 jwt 模块,并配置密钥和令牌有效期 11 | secretOrPrivateKey: 'secretKey', 12 | signOptions: { 13 | expiresIn: 3600 14 | } 15 | }), 16 | forwardRef(() => UserModule) // 处理模块间的循环依赖 17 | ], 18 | providers: [AuthService, AuthStrategy], 19 | exports: [AuthService] // 导出 AuthServie 供 UserModule 使用 20 | }) 21 | export class AuthModule { } -------------------------------------------------------------------------------- /demo/easy-post/src/core/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | 4 | import { User } from '../../feature/user/user.entity'; 5 | import { UserService } from '../../feature/user/user.service'; 6 | 7 | @Injectable() 8 | export class AuthService { 9 | constructor( 10 | @Inject(UserService) private readonly userService: UserService, 11 | @Inject(JwtService) private readonly jwtService: JwtService, 12 | ) { } 13 | 14 | async createToken(payload: { account: string }): Promise { 15 | return this.jwtService.sign(payload); 16 | } 17 | 18 | async validateUser(payload: { account: string }): Promise { 19 | return await this.userService.findOneByAccount(payload.account); 20 | } 21 | } -------------------------------------------------------------------------------- /demo/easy-post/src/core/auth/auth.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { ExtractJwt, Strategy } from 'passport-jwt'; 4 | 5 | import { AuthService } from './auth.service'; 6 | 7 | @Injectable() 8 | export class AuthStrategy extends PassportStrategy(Strategy) { 9 | /** 10 | * 这里的构造函数向父类传递了授权时必要的参数,在实例化时,父类会得知授权时,客户端的请求必须使用 Authorization 作为请求头, 11 | * 而这个请求头的内容前缀也必须为 Bearer,在解码授权令牌时,使用秘钥 secretOrKey: 'secretKey' 来将授权令牌解码为创建令牌时的 payload。 12 | */ 13 | constructor(private readonly authService: AuthService) { 14 | super({ 15 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 16 | secretOrKey: 'secretKey', 17 | }); 18 | } 19 | 20 | /** 21 | * validate 方法实现了父类的抽象方法,在解密授权令牌成功后,即本次请求的授权令牌是没有过期的, 22 | * 此时会将解密后的 payload 作为参数传递给 validate 方法,这个方法需要做具体的授权逻辑,比如这里我使用了通过用户名查找用户是否存在。 23 | * 当用户不存在时,说明令牌有误,可能是被伪造了,此时需抛出 UnauthorizedException 未授权异常。 24 | * 当用户存在时,会将 user 对象添加到 req 中,在之后的 req 对象中,可以使用 req.user 获取当前登录用户。 25 | */ 26 | async validate(payload: { account: string }) { 27 | const user = await this.authService.validateUser(payload); 28 | if (!user) { 29 | throw new UnauthorizedException(); 30 | } 31 | return user; 32 | } 33 | } -------------------------------------------------------------------------------- /demo/easy-post/src/core/guards/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | 4 | import { User } from '../../feature/user/user.entity'; 5 | 6 | @Injectable() 7 | export class RolesGuard implements CanActivate { 8 | constructor( 9 | @Inject(Reflector) private readonly reflector: Reflector 10 | ) { } 11 | 12 | async canActivate(context: ExecutionContext): Promise { 13 | // 通过反射获取请求路由是否添加了 @Roles() 装饰器,如果没有添加,则代表不需要进行认证 14 | const roles = this.reflector.get('roles', context.getHandler()); 15 | if (!roles) { 16 | return true; 17 | } 18 | 19 | // 在请求对象中获取 user 对象,此 user 对象是 AuthStrategy 中 validate 方法成功执行后的返回值 20 | const request = context.switchToHttp().getRequest(); 21 | const user: User = request.user; 22 | 23 | // 判断当前请求用户的角色是否为管理员 24 | const hasRole = () => user.role === 'admin'; 25 | return user && hasRole(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /demo/easy-post/src/core/interceptors/errors.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, HttpException, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { catchError } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class ErrorsInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | return next.handle().pipe(catchError((error, caught): any => { 9 | if (error instanceof HttpException) { 10 | return Promise.resolve({ 11 | code: error.getStatus(), 12 | message: error.getResponse() 13 | }); 14 | } 15 | return Promise.resolve({ 16 | code: 500, 17 | message: `出现了意外错误:${error.toString()}` 18 | }); 19 | })); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/easy-post/src/feature/post/post.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Inject, Param, Post, Put, Req, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { Request } from 'express'; 4 | 5 | import { Roles } from '../../common/decorators/roles.decorator'; 6 | import { Result } from '../../common/interfaces/result.interface'; 7 | import { RolesGuard } from '../../core/guards/roles.guard'; 8 | import { Post as PostEntity } from './post.entity'; 9 | import { PostService } from './post.service'; 10 | 11 | @Controller('post') 12 | export class PostController { 13 | constructor( 14 | @Inject(PostService) private readonly postService: PostService 15 | ) { } 16 | 17 | @Post() 18 | @UseGuards(AuthGuard()) 19 | async createPost(@Req() req: Request, @Body() createInput: PostEntity): Promise { 20 | createInput.user = req.user; 21 | await this.postService.create(createInput); 22 | return { code: 200, message: '创建帖子成功' }; 23 | } 24 | 25 | @Delete(':id') 26 | @Roles('admin') 27 | @UseGuards(AuthGuard(), RolesGuard) 28 | async remove(@Param() id: number): Promise { 29 | await this.postService.remove(id); 30 | return { code: 200, message: '删除帖子成功' }; 31 | } 32 | 33 | @Put(':id') 34 | @Roles('admin') 35 | @UseGuards(AuthGuard(), RolesGuard) 36 | async update(@Param() id: number, @Body() updateInput: PostEntity): Promise { 37 | await this.postService.update(id, updateInput); 38 | return { code: 200, message: '更新帖子成功' }; 39 | } 40 | 41 | @Get() 42 | @UseGuards(AuthGuard()) 43 | async findAll(@Req() req: Request): Promise { 44 | const data = await this.postService.findAll(req.user.id); 45 | return { code: 200, message: '查询所有帖子成功', data }; 46 | } 47 | 48 | @Get(':id') 49 | async findOne(@Param() id: number): Promise { 50 | const data = await this.postService.findOneById(id); 51 | return { code: 200, message: '查询帖子成功', data }; 52 | } 53 | } -------------------------------------------------------------------------------- /demo/easy-post/src/feature/post/post.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | import { User } from '../user/user.entity'; 4 | 5 | @Entity('post') 6 | export class Post { 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | 10 | @Column() 11 | title: string; 12 | 13 | @Column({ 14 | type: 'text' 15 | }) 16 | content: string; 17 | 18 | // 在 @ManyToOne 一侧,即在外键拥有者一侧,设置 onDelete,就可以使用外键的级联功能,这里设置级联删除,当删除 user 时,user 的所有 post 会被级联删除 19 | @ManyToOne(type => User, user => user.posts, { 20 | onDelete: 'CASCADE' 21 | }) 22 | user: User; 23 | } -------------------------------------------------------------------------------- /demo/easy-post/src/feature/post/post.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | 5 | import { PostController } from './post.controller'; 6 | import { Post } from './post.entity'; 7 | import { PostService } from './post.service'; 8 | 9 | @Module({ 10 | imports: [ 11 | // 向帖子模块注册 passport,并配置默认策略为 jwt,因为覆盖了默认的策略,所以要在每个使用 @AuthGuard() 的模块导入 PassportModule 12 | PassportModule.register({ defaultStrategy: 'jwt' }), 13 | TypeOrmModule.forFeature([Post]) 14 | ], 15 | providers: [PostService], 16 | controllers: [PostController] 17 | }) 18 | export class PostModule { } -------------------------------------------------------------------------------- /demo/easy-post/src/feature/post/post.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | 5 | import { Post } from './post.entity'; 6 | 7 | @Injectable() 8 | export class PostService { 9 | constructor( 10 | @InjectRepository(Post) private readonly postRepo: Repository 11 | ) { } 12 | 13 | /** 14 | * 创建帖子 15 | * 16 | * @param createInput createInput 17 | */ 18 | async create(createInput: Post): Promise { 19 | await this.postRepo.save(createInput); 20 | } 21 | 22 | /** 23 | * 删除帖子 24 | * 25 | * @param id 帖子ID 26 | */ 27 | async remove(id: number): Promise { 28 | const existing = await this.findOneById(id); 29 | if (!existing) throw new HttpException(`删除失败,ID 为 '${id}' 的帖子不存在`, 404); 30 | await this.postRepo.remove(existing); 31 | } 32 | 33 | /** 34 | * 更新帖子 35 | * 36 | * @param id 帖子ID 37 | * @param updateInput updateInput 38 | */ 39 | async update(id: number, updateInput: Post): Promise { 40 | const existing = await this.findOneById(id); 41 | if (!existing) throw new HttpException(`更新失败,ID 为 '${id}' 的帖子不存在`, 404); 42 | if (updateInput.title) existing.title = updateInput.title; 43 | if (updateInput.content) existing.content = updateInput.content; 44 | await this.postRepo.save(existing); 45 | } 46 | 47 | /** 48 | * 查询帖子 49 | * 50 | * @param id 帖子ID 51 | */ 52 | async findOneById(id: number): Promise { 53 | return await this.postRepo.findOne(id); 54 | } 55 | 56 | /** 57 | * 查询用户的所有帖子 58 | * 59 | * @param userId 用户ID 60 | */ 61 | async findAll(userId: number): Promise { 62 | return await this.postRepo.find({ where: { user: { id: userId } } }); 63 | } 64 | } -------------------------------------------------------------------------------- /demo/easy-post/src/feature/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Inject, Param, Post, Put, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | import { Roles } from '../../common/decorators/roles.decorator'; 5 | import { Result } from '../../common/interfaces/result.interface'; 6 | import { AuthService } from '../../core/auth/auth.service'; 7 | import { RolesGuard } from '../../core/guards/roles.guard'; 8 | import { User } from './user.entity'; 9 | import { UserService } from './user.service'; 10 | 11 | @Controller('user') 12 | export class UserController { 13 | constructor( 14 | @Inject(AuthService) private readonly authService: AuthService, 15 | @Inject(UserService) private readonly userService: UserService 16 | ) { } 17 | 18 | /** 19 | * 用户登录成功后,返回的 data 是授权令牌; 20 | * 在调用有 @UseGuards(AuthGuard()) 装饰的路由时,会检查当前请求头中是否包含 Authorization: Bearer xxx 授权令牌, 21 | * 其中 Authorization 是用于告诉服务端本次请求有令牌,并且令牌前缀是 Bearer,而令牌的具体内容是登录之后返回的 data(accessToken)。 22 | */ 23 | @Post('login') 24 | async login(@Body() body: { account: string, password: string }): Promise { 25 | await this.userService.login(body.account, body.password); 26 | const accessToken = await this.authService.createToken({ account: body.account }); 27 | return { code: 200, message: '登录成功', data: accessToken }; 28 | } 29 | 30 | @Post('register') 31 | async register(@Body() user: User): Promise { 32 | await this.userService.register(user); 33 | return { code: 200, message: '注册成功' }; 34 | } 35 | 36 | @Delete(':id') 37 | @Roles('admin') 38 | @UseGuards(AuthGuard(), RolesGuard) 39 | async remove(@Param() id: number): Promise { 40 | await this.userService.remove(id); 41 | return { code: 200, message: '删除用户成功' }; 42 | } 43 | 44 | @Put(':id') 45 | @Roles('admin') 46 | @UseGuards(AuthGuard(), RolesGuard) 47 | async update(@Param() id: number, updateInput: User): Promise { 48 | await this.userService.update(id, updateInput); 49 | return { code: 200, message: '更新用户成功' }; 50 | } 51 | 52 | @Get(':id') 53 | async findOne(@Param() id: number): Promise { 54 | const data = await this.userService.findOneWithPostsById(id); 55 | return { code: 200, message: '查询用户成功', data }; 56 | } 57 | 58 | @Get() 59 | @Roles('admin') 60 | @UseGuards(AuthGuard(), RolesGuard) 61 | async findAll(): Promise { 62 | const data = await this.userService.findAll(); 63 | return { code: 200, message: '查询所有用户成功', data }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /demo/easy-post/src/feature/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | import { Post } from '../post/post.entity'; 4 | 5 | @Entity('user') 6 | export class User { 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | 10 | @Column() 11 | account: string; 12 | 13 | @Column() 14 | password: string; 15 | 16 | @Column() 17 | name: string; 18 | 19 | @OneToMany(type => Post, post => post.user) 20 | posts: Post[]; 21 | 22 | @Column({ 23 | default: 'regular' 24 | }) 25 | role: string; 26 | } -------------------------------------------------------------------------------- /demo/easy-post/src/feature/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | 5 | import { CommonModule } from '../../common/common.module'; 6 | import { AuthModule } from '../../core/auth/auth.module'; 7 | import { UserController } from './user.controller'; 8 | import { User } from './user.entity'; 9 | import { UserService } from './user.service'; 10 | 11 | @Module({ 12 | imports: [ 13 | // 向用户模块注册 passport,并配置默认策略为 jwt,因为覆盖了默认的策略,所以要在每个使用 @AuthGuard() 的模块导入 PassportModule 14 | PassportModule.register({ defaultStrategy: 'jwt' }), 15 | TypeOrmModule.forFeature([User]), 16 | forwardRef(() => AuthModule), // 处理模块间的循环依赖 17 | CommonModule 18 | ], 19 | providers: [UserService], 20 | controllers: [UserController], 21 | exports: [UserService] 22 | }) 23 | export class UserModule { } -------------------------------------------------------------------------------- /demo/easy-post/src/feature/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, Inject, Injectable, OnModuleInit } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | 5 | import { CryptoUtil } from '../../common/utils/crypto.util'; 6 | import { User } from './user.entity'; 7 | 8 | @Injectable() 9 | export class UserService implements OnModuleInit { 10 | async onModuleInit() { 11 | if (await this.findOneByAccount('admin')) return; 12 | // 初始化系统管理员 13 | const admin = this.userRepo.create({ 14 | account: 'admin', 15 | password: this.cryptoUtil.encryptPassword('i_am_admin_!'), 16 | name: '系统管理员', 17 | role: 'admin' 18 | }); 19 | await this.userRepo.save(admin); 20 | } 21 | 22 | constructor( 23 | @InjectRepository(User) private readonly userRepo: Repository, 24 | @Inject(CryptoUtil) private readonly cryptoUtil: CryptoUtil 25 | ) { } 26 | 27 | /** 28 | * 用户登录 29 | * 30 | * @param account 登录账号 31 | * @param password 登录密码 32 | */ 33 | async login(account: string, password: string): Promise { 34 | const user = await this.findOneByAccount(account); 35 | if (!user) throw new HttpException('登录账号有误', 406); 36 | if (!this.cryptoUtil.checkPassword(password, user.password)) throw new HttpException('登录密码有误', 406); 37 | } 38 | 39 | /** 40 | * 用户注册 41 | * 42 | * @param user 用户信息 43 | */ 44 | async register(user: User): Promise { 45 | const existing = await this.findOneByAccount(user.account); 46 | if (existing) throw new HttpException('账号已存在', 409); 47 | user.password = this.cryptoUtil.encryptPassword(user.password); 48 | await this.userRepo.save(this.userRepo.create(user)); 49 | } 50 | 51 | /** 52 | * 删除用户 53 | * 54 | * @param id 用户ID 55 | */ 56 | async remove(id: number): Promise { 57 | const existing = await this.userRepo.findOne(id); 58 | if (!existing) throw new HttpException(`删除失败,ID 为 '${id}' 的用户不存在`, 404); 59 | await this.userRepo.remove(existing); 60 | } 61 | 62 | /** 63 | * 更新用户 64 | * 65 | * @param id 用户ID 66 | * @param updateInput updateInput 67 | */ 68 | async update(id: number, updateInput: User) { 69 | const existing = await this.userRepo.findOne(id); 70 | if (!existing) throw new HttpException(`更新失败,ID 为 '${id}' 的用户不存在`, 404); 71 | if (updateInput.account) existing.account = updateInput.account; 72 | if (updateInput.password) existing.password = this.cryptoUtil.encryptPassword(updateInput.password); 73 | if (updateInput.name) existing.name = updateInput.name; 74 | await this.userRepo.save(existing); 75 | } 76 | 77 | /** 78 | * 通过登录账号查询用户 79 | * 80 | * @param account 登录账号 81 | */ 82 | async findOneByAccount(account: string): Promise { 83 | return await this.userRepo.findOne({ account }); 84 | } 85 | 86 | /** 87 | * 查询用户及其帖子的信息 88 | * 89 | * @param id 用户ID 90 | */ 91 | async findOneWithPostsById(id: number): Promise { 92 | return await this.userRepo.findOne(id, { relations: ['posts'] }); 93 | } 94 | 95 | /** 96 | * 查询所有用户 97 | */ 98 | async findAll(): Promise { 99 | return await this.userRepo.find(); 100 | } 101 | } -------------------------------------------------------------------------------- /demo/easy-post/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | 4 | import { AppModule } from './app.module'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | app.listen(5000).then(() => { 9 | new Logger('EasyPost').log('EasyPost API server has been started on http://localhost:5000'); 10 | }); 11 | } 12 | 13 | bootstrap(); -------------------------------------------------------------------------------- /demo/easy-post/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "baseUrl": "./", 5 | "declaration": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "noImplicitAny": false, 10 | "noLib": false, 11 | "lib": [ 12 | "es2017", 13 | "esnext.asynciterable" 14 | ], 15 | "noUnusedLocals": false, 16 | "removeComments": true, 17 | "strict": false, 18 | "strictPropertyInitialization": false, 19 | "target": "es2017", 20 | "outDir": "dist" 21 | }, 22 | "include": [ 23 | "src/*.ts", 24 | "src/**/*.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "test" 29 | ] 30 | } -------------------------------------------------------------------------------- /demo/easy-post/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": { 7 | "no-unused-expression": true 8 | }, 9 | "rules": { 10 | "array-type": [ 11 | true, 12 | "array" 13 | ], 14 | "ban-types": { 15 | "options": [ 16 | [ 17 | "Object", 18 | "Avoid using the `Object` type. Did you mean `object`?" 19 | ], 20 | [ 21 | "Function", 22 | "Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`." 23 | ], 24 | [ 25 | "Boolean", 26 | "Avoid using the `Boolean` type. Did you mean `boolean`?" 27 | ], 28 | [ 29 | "Number", 30 | "Avoid using the `Number` type. Did you mean `number`?" 31 | ], 32 | [ 33 | "String", 34 | "Avoid using the `String` type. Did you mean `string`?" 35 | ] 36 | ] 37 | }, 38 | "class-name": true, 39 | "comment-format": [ 40 | true, 41 | "check-space" 42 | ], 43 | "curly": [ 44 | true, 45 | "ignore-same-line" 46 | ], 47 | "indent": [ 48 | true, 49 | "spaces", 50 | 4 51 | ], 52 | "max-line-length": [ 53 | true, 54 | 150 55 | ], 56 | "quotemark": [ 57 | true, 58 | "single" 59 | ], 60 | "semicolon": [ 61 | true, 62 | "always" 63 | ], 64 | "interface-name": [ 65 | false 66 | ], 67 | "interface-over-type-literal": true, 68 | "jsdoc-format": true, 69 | "linebreak-style": false, 70 | "next-line": false, 71 | "no-inferrable-types": true, 72 | "no-internal-module": true, 73 | "no-null-keyword": true, 74 | "no-switch-case-fall-through": true, 75 | "no-trailing-whitespace": [ 76 | true, 77 | "ignore-template-strings" 78 | ], 79 | "no-var-keyword": true, 80 | "object-literal-shorthand": true, 81 | "one-line": [ 82 | true, 83 | "check-open-brace", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "prefer-conditional-expression": [ 88 | true, 89 | "check-else-if" 90 | ], 91 | "prefer-for-of": true, 92 | "space-within-parens": true, 93 | "triple-equals": true, 94 | "typedef-whitespace": [ 95 | true, 96 | { 97 | "call-signature": "nospace", 98 | "index-signature": "nospace", 99 | "parameter": "nospace", 100 | "property-declaration": "nospace", 101 | "variable-declaration": "nospace" 102 | } 103 | ], 104 | "whitespace": [ 105 | true, 106 | "check-branch", 107 | "check-decl", 108 | "check-operator", 109 | "check-separator", 110 | "check-type" 111 | ], 112 | "no-implicit-dependencies": false, 113 | "object-literal-key-quotes": [ 114 | true, 115 | "consistent-as-needed" 116 | ], 117 | "variable-name": [ 118 | true, 119 | "ban-keywords", 120 | "check-format", 121 | "allow-leading-underscore", 122 | "allow-pascal-case" 123 | ], 124 | "arrow-parens": false, 125 | "arrow-return-shorthand": true, 126 | "forin": false, 127 | "member-access": false, 128 | "no-conditional-assignment": false, 129 | "no-console": false, 130 | "no-debugger": false, 131 | "no-empty": false, 132 | "no-empty-interface": false, 133 | "no-eval": false, 134 | "no-object-literal-type-assertion": false, 135 | "no-shadowed-variable": false, 136 | "no-submodule-imports": false, 137 | "no-var-requires": false, 138 | "ordered-imports": false, 139 | "radix": false, 140 | "trailing-comma": false, 141 | "align": false, 142 | "eofline": false, 143 | "no-consecutive-blank-lines": false, 144 | "space-before-function-paren": false, 145 | "ban-comma-operator": false, 146 | "max-classes-per-file": false, 147 | "member-ordering": false, 148 | "no-angle-bracket-type-assertion": false, 149 | "no-bitwise": false, 150 | "no-namespace": false, 151 | "no-reference": false, 152 | "object-literal-sort-keys": false, 153 | "one-variable-per-declaration": false, 154 | "type-operator-spacing": false, 155 | "no-type-assertion-whitespace": false, 156 | "object-literal-surrounding-space": false, 157 | "no-increment-decrement": false, 158 | "no-in-operator": false, 159 | "no-double-space": false, 160 | "no-unnecessary-type-assertion-2": false, 161 | "no-bom": false, 162 | "boolean-trivia": false, 163 | "debug-assert": false, 164 | "no-unused-expression": false 165 | } 166 | } -------------------------------------------------------------------------------- /demo/graphql-api/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL API Demo 2 | 3 | ## 启动 4 | 5 | ```bash 6 | # npm install 7 | $ npm install 8 | 9 | # npm run start 10 | $ npm run start 11 | ``` 12 | 13 | ## GraphQL Playground 请求示例 14 | 15 | ### 创建 16 | 17 | ![创建](./images/createCat.png) 18 | 19 | ### 删除 20 | 21 | ![删除](./images/deleteCat.png) 22 | 23 | ### 更新 24 | 25 | ![更新](./images/updateCat.png) 26 | 27 | ### 查询 28 | 29 | ![查询单个](./images/findOneCat.png) 30 | 31 | ![查询所有](./images/findCats.png) -------------------------------------------------------------------------------- /demo/graphql-api/images/createCat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzzzzzy/Nestjs-Learning/70800dacebf241f2708561776e4b86fb8e14ffed/demo/graphql-api/images/createCat.png -------------------------------------------------------------------------------- /demo/graphql-api/images/deleteCat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzzzzzy/Nestjs-Learning/70800dacebf241f2708561776e4b86fb8e14ffed/demo/graphql-api/images/deleteCat.png -------------------------------------------------------------------------------- /demo/graphql-api/images/findCats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzzzzzy/Nestjs-Learning/70800dacebf241f2708561776e4b86fb8e14ffed/demo/graphql-api/images/findCats.png -------------------------------------------------------------------------------- /demo/graphql-api/images/findOneCat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzzzzzy/Nestjs-Learning/70800dacebf241f2708561776e4b86fb8e14ffed/demo/graphql-api/images/findOneCat.png -------------------------------------------------------------------------------- /demo/graphql-api/images/updateCat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzzzzzy/Nestjs-Learning/70800dacebf241f2708561776e4b86fb8e14ffed/demo/graphql-api/images/updateCat.png -------------------------------------------------------------------------------- /demo/graphql-api/nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "exec": "node --inspect-brk -r ts-node/register src/main.ts" 10 | } -------------------------------------------------------------------------------- /demo/graphql-api/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 10 | } -------------------------------------------------------------------------------- /demo/graphql-api/ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": 5432, 5 | "username": "postgres", 6 | "password": "123456", 7 | "database": "test", 8 | "entities": [ 9 | "src/**/**.entity{.ts,.js}" 10 | ], 11 | "synchronize": true, 12 | "logging": true 13 | } -------------------------------------------------------------------------------- /demo/graphql-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-apollo-graphql", 3 | "version": "1.0.0", 4 | "description": "This is a nestjs graphql demo.", 5 | "main": "main.ts", 6 | "scripts": { 7 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 8 | "start:watch": "nodemon", 9 | "debug": "node --inspect-brk -r ts-node/register src/main.ts", 10 | "debug:watch": "nodemon --config nodemon-debug.json", 11 | "check": "tslint -p tsconfig.json -c tslint.json", 12 | "fix": "tslint -p tsconfig.json -c tslint.json --fix" 13 | }, 14 | "author": "dzzzzzy", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@nestjs/common": "^6.0.0", 18 | "@nestjs/core": "^6.0.0", 19 | "@nestjs/graphql": "^6.0.0", 20 | "@nestjs/platform-express": "^6.0.0", 21 | "@nestjs/typeorm": "^7.0.0", 22 | "apollo-server-express": "^2.4.8", 23 | "graphql": "^15.0.0", 24 | "graphql-subscriptions": "^1.0.0", 25 | "graphql-tools": "^5.0.0", 26 | "pg": "^7.9.0", 27 | "reflect-metadata": "^0.1.13", 28 | "rxjs": "^6.4.0", 29 | "subscriptions-transport-ws": "^0.9.16", 30 | "typeorm": "^0.2.15", 31 | "typescript": "^3.3.3333" 32 | }, 33 | "devDependencies": { 34 | "@types/graphql": "^14.0.7", 35 | "@types/node": "^11.11.3", 36 | "nodemon": "^1.18.10", 37 | "ts-node": "^8.0.3", 38 | "tsconfig-paths": "^3.8.0", 39 | "tslint": "^5.14.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /demo/graphql-api/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { APP_INTERCEPTOR } from '@nestjs/core'; 3 | import { GraphQLModule } from '@nestjs/graphql'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | 6 | import { AppResolver } from './app.resolver'; 7 | import { AppService } from './app.service'; 8 | import { CatModule } from './cat/cat.module'; 9 | import { ErrorsInterceptor } from './common/errors.interceptor'; 10 | import { PubSubFactory } from './pub-sub.provider'; 11 | 12 | @Module({ 13 | imports: [ 14 | GraphQLModule.forRoot({ 15 | typePaths: ['./**/*.graphql'], 16 | installSubscriptionHandlers: true 17 | }), 18 | TypeOrmModule.forRoot(), 19 | CatModule 20 | ], 21 | providers: [ 22 | { 23 | provide: APP_INTERCEPTOR, 24 | useClass: ErrorsInterceptor 25 | }, 26 | PubSubFactory, 27 | AppResolver, AppService 28 | ] 29 | }) 30 | export class AppModule { } 31 | -------------------------------------------------------------------------------- /demo/graphql-api/src/app.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql'; 3 | import { PubSub } from 'graphql-subscriptions'; 4 | 5 | import { AppService } from './app.service'; 6 | 7 | @Resolver('app') 8 | export class AppResolver { 9 | constructor( 10 | @Inject(AppService) private readonly appService: AppService, 11 | @Inject('PubSub') private readonly pubSub: PubSub, 12 | ) { } 13 | 14 | @Query('sayHello') 15 | async sayHello(@Args() args: { name: string }) { 16 | return this.appService.sayHello(args.name); 17 | } 18 | 19 | @Mutation('pubMessage') 20 | async sayHi(@Args('msg') args: string) { 21 | this.pubSub.publish('subMessage', { subMessage: `msg: ${args}` }); 22 | return `msg: ${args}`; 23 | } 24 | 25 | @Subscription('subMessage') 26 | subMessage() { 27 | return this.pubSub.asyncIterator('subMessage'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/graphql-api/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | async sayHello(name: string) { 6 | return `Hello ${name}!`; 7 | } 8 | } -------------------------------------------------------------------------------- /demo/graphql-api/src/app.types.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | sayHello(name: String!): String 3 | } 4 | 5 | type Mutation { 6 | pubMessage(msg: String!): String 7 | } 8 | 9 | type Subscription { 10 | subMessage: String 11 | } -------------------------------------------------------------------------------- /demo/graphql-api/src/cat/cat.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity('cat') 4 | export class Cat { 5 | /** 6 | * 自增主键 7 | */ 8 | @PrimaryGeneratedColumn({ 9 | comment: '自增ID' 10 | }) 11 | id: number; 12 | 13 | /** 14 | * 昵称 15 | */ 16 | @Column({ 17 | comment: '昵称' 18 | }) 19 | nickname: string; 20 | 21 | /** 22 | * 品种 23 | */ 24 | @Column({ 25 | comment: '品种' 26 | }) 27 | species: string; 28 | } -------------------------------------------------------------------------------- /demo/graphql-api/src/cat/cat.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { Cat } from './cat.entity'; 5 | import { CatResolver } from './cat.resolver'; 6 | import { CatService } from './cat.service'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([Cat])], 10 | providers: [CatResolver, CatService], 11 | }) 12 | export class CatModule { } -------------------------------------------------------------------------------- /demo/graphql-api/src/cat/cat.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; 3 | 4 | import { Result } from '../common/result.interface'; 5 | import { Cat } from './cat.entity'; 6 | import { CatService } from './cat.service'; 7 | 8 | @Resolver('Cat') 9 | export class CatResolver { 10 | constructor( 11 | @Inject(CatService) private readonly catService: CatService, 12 | ) { } 13 | 14 | @Mutation('createCat') 15 | async createCat(@Args('cat') cat: Cat): Promise { 16 | await this.catService.createCat(cat); 17 | return { code: 200, message: '创建成功' }; 18 | } 19 | 20 | @Mutation('deleteCat') 21 | async deleteCat(@Args('id') id: number): Promise { 22 | await this.catService.deleteCat(id); 23 | return { code: 200, message: '删除成功' }; 24 | } 25 | 26 | @Mutation('updateCat') 27 | async updateCat(@Args() updateInput: { id: number, cat: Cat }): Promise { 28 | await this.catService.updateCat(updateInput.id, updateInput.cat); 29 | return { code: 200, message: '更新成功' }; 30 | } 31 | 32 | @Query('findOneCat') 33 | async findOneCat(@Args('id') id: number): Promise { 34 | const data = await this.catService.findOneCat(id); 35 | return { code: 200, message: '查询成功', data }; 36 | } 37 | 38 | @Query('findCats') 39 | async findCats(): Promise { 40 | const data = await this.catService.findCats(); 41 | return { code: 200, message: '查询成功', data }; 42 | } 43 | } -------------------------------------------------------------------------------- /demo/graphql-api/src/cat/cat.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | 5 | import { Cat } from './cat.entity'; 6 | 7 | @Injectable() 8 | export class CatService { 9 | constructor( 10 | @InjectRepository(Cat) private readonly catRepo: Repository, // 使用泛型注入对应类型的存储库实例 11 | ) { } 12 | 13 | /** 14 | * 创建 15 | * 16 | * @param cat Cat 实体对象 17 | */ 18 | async createCat(cat: Cat): Promise { 19 | /** 20 | * 创建新的实体实例,并将此对象的所有实体属性复制到新实体中。 请注意,它仅复制实体模型中存在的属性。 21 | */ 22 | // this.catRepo.create(cat); 23 | 24 | return this.catRepo.save(this.catRepo.create(cat)); 25 | 26 | /** 27 | * 将给定实体插入数据库。与save方法不同,执行原始操作时不包括级联,关系和其他操作。 28 | * 执行快速有效的INSERT操作。不检查数据库中是否存在实体,因此如果插入重复实体,本次操作将失败。 29 | */ 30 | // await this.catRepo.insert(cat); 31 | } 32 | 33 | /** 34 | * 删除 35 | * 36 | * @param id ID 37 | */ 38 | async deleteCat(id: number): Promise { 39 | await this.findOneById(id); 40 | this.catRepo.delete(id); 41 | } 42 | 43 | /** 44 | * 更新 45 | * 46 | * @param cat Cat 实体对象 47 | */ 48 | async updateCat(id: number, cat: Cat): Promise { 49 | const existCat = await this.findOneById(id); 50 | // 当传入空数据时,避免覆盖原数据 51 | existCat.nickname = cat && cat.nickname ? cat.nickname : existCat.nickname; 52 | existCat.species = cat && cat.species ? cat.species : existCat.species; 53 | this.catRepo.save(existCat); 54 | } 55 | 56 | /** 57 | * 根据ID查询 58 | * 59 | * @param id ID 60 | */ 61 | async findOneCat(id: number): Promise { 62 | return this.findOneById(id); 63 | } 64 | 65 | /** 66 | * 查询所有 67 | */ 68 | async findCats(): Promise { 69 | return this.catRepo.find(); 70 | } 71 | 72 | /** 73 | * 根据ID查询单个信息,如果不存在则抛出404异常 74 | * @param id ID 75 | */ 76 | private async findOneById(id: number): Promise { 77 | const catInfo = await this.catRepo.findOne(id); 78 | if (!catInfo) { 79 | throw new HttpException(`指定 id=${id} 的猫猫不存在`, 404); 80 | } 81 | return catInfo; 82 | } 83 | } -------------------------------------------------------------------------------- /demo/graphql-api/src/cat/cat.types.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | # #是代码里的注释,""" """是 graphql 文档中的注释 3 | """查询一个猫猫""" 4 | findOneCat(id: Int!): CatResponse 5 | findCats: CatsResponse 6 | } 7 | 8 | type Mutation { 9 | createCat(cat: CatInput!): CommonResponse 10 | deleteCat(id: Int!): CommonResponse 11 | updateCat(id: Int!, cat: CatInput): CommonResponse 12 | } 13 | 14 | type CommonResponse { 15 | code: Int 16 | message: String 17 | } 18 | 19 | type CatResponse { 20 | code: Int 21 | message: String 22 | data: Cat 23 | } 24 | 25 | type CatsResponse { 26 | code: Int 27 | message: String 28 | data: [Cat] 29 | } 30 | 31 | type Cat { 32 | id: Int 33 | nickname: String 34 | species: String 35 | } 36 | 37 | input CatInput { 38 | nickname: String 39 | species: String 40 | } -------------------------------------------------------------------------------- /demo/graphql-api/src/common/errors.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, HttpException, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { catchError } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class ErrorsInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | return next.handle().pipe(catchError((error, caught): any => { 9 | if (error instanceof HttpException) { 10 | return Promise.resolve({ 11 | code: error.getStatus(), 12 | message: error.getResponse() 13 | }); 14 | } 15 | return Promise.resolve({ 16 | code: 500, 17 | message: `出现了意外错误:${error.toString()}` 18 | }); 19 | })); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/graphql-api/src/common/result.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Result { 2 | code: number; 3 | message: string; 4 | data?: any; 5 | } 6 | -------------------------------------------------------------------------------- /demo/graphql-api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | 4 | import { AppModule } from './app.module'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | 9 | await app.listen(3000, '0.0.0.0', () => { 10 | Logger.log('GraphQL api server started on: http://localhost:3000/graphql'); 11 | }); 12 | } 13 | 14 | bootstrap(); -------------------------------------------------------------------------------- /demo/graphql-api/src/pub-sub.provider.ts: -------------------------------------------------------------------------------- 1 | import { PubSub } from 'graphql-subscriptions'; 2 | 3 | export const PubSubFactory = { 4 | provide: 'PubSub', 5 | useFactory: () => { 6 | return new PubSub(); 7 | } 8 | }; -------------------------------------------------------------------------------- /demo/graphql-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "baseUrl": "./", 5 | "declaration": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "noImplicitAny": false, 10 | "noLib": false, 11 | "lib": [ 12 | "es2017", 13 | "esnext.asynciterable" 14 | ], 15 | "noUnusedLocals": false, 16 | "removeComments": true, 17 | "strict": false, 18 | "strictPropertyInitialization": false, 19 | "target": "es2017", 20 | "outDir": "dist" 21 | }, 22 | "include": [ 23 | "src/*.ts", 24 | "src/**/*.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "test" 29 | ] 30 | } -------------------------------------------------------------------------------- /demo/graphql-api/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": { 7 | "no-unused-expression": true 8 | }, 9 | "rules": { 10 | "array-type": [ 11 | true, 12 | "array" 13 | ], 14 | "ban-types": { 15 | "options": [ 16 | [ 17 | "Object", 18 | "Avoid using the `Object` type. Did you mean `object`?" 19 | ], 20 | [ 21 | "Function", 22 | "Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`." 23 | ], 24 | [ 25 | "Boolean", 26 | "Avoid using the `Boolean` type. Did you mean `boolean`?" 27 | ], 28 | [ 29 | "Number", 30 | "Avoid using the `Number` type. Did you mean `number`?" 31 | ], 32 | [ 33 | "String", 34 | "Avoid using the `String` type. Did you mean `string`?" 35 | ] 36 | ] 37 | }, 38 | "class-name": true, 39 | "comment-format": [ 40 | true, 41 | "check-space" 42 | ], 43 | "curly": [ 44 | true, 45 | "ignore-same-line" 46 | ], 47 | "indent": [ 48 | true, 49 | "spaces", 50 | 4 51 | ], 52 | "max-line-length": [ 53 | true, 54 | 150 55 | ], 56 | "quotemark": [ 57 | true, 58 | "single" 59 | ], 60 | "semicolon": [ 61 | true, 62 | "always" 63 | ], 64 | "interface-name": [ 65 | false 66 | ], 67 | "interface-over-type-literal": true, 68 | "jsdoc-format": true, 69 | "linebreak-style": false, 70 | "next-line": false, 71 | "no-inferrable-types": true, 72 | "no-internal-module": true, 73 | "no-null-keyword": true, 74 | "no-switch-case-fall-through": true, 75 | "no-trailing-whitespace": [ 76 | true, 77 | "ignore-template-strings" 78 | ], 79 | "no-var-keyword": true, 80 | "object-literal-shorthand": true, 81 | "one-line": [ 82 | true, 83 | "check-open-brace", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "prefer-conditional-expression": [ 88 | true, 89 | "check-else-if" 90 | ], 91 | "prefer-for-of": true, 92 | "space-within-parens": true, 93 | "triple-equals": true, 94 | "typedef-whitespace": [ 95 | true, 96 | { 97 | "call-signature": "nospace", 98 | "index-signature": "nospace", 99 | "parameter": "nospace", 100 | "property-declaration": "nospace", 101 | "variable-declaration": "nospace" 102 | } 103 | ], 104 | "whitespace": [ 105 | true, 106 | "check-branch", 107 | "check-decl", 108 | "check-operator", 109 | "check-separator", 110 | "check-type" 111 | ], 112 | "no-implicit-dependencies": false, 113 | "object-literal-key-quotes": [ 114 | true, 115 | "consistent-as-needed" 116 | ], 117 | "variable-name": [ 118 | true, 119 | "ban-keywords", 120 | "check-format", 121 | "allow-leading-underscore", 122 | "allow-pascal-case" 123 | ], 124 | "arrow-parens": false, 125 | "arrow-return-shorthand": true, 126 | "forin": false, 127 | "member-access": false, 128 | "no-conditional-assignment": false, 129 | "no-console": false, 130 | "no-debugger": false, 131 | "no-empty": false, 132 | "no-empty-interface": false, 133 | "no-eval": false, 134 | "no-object-literal-type-assertion": false, 135 | "no-shadowed-variable": false, 136 | "no-submodule-imports": false, 137 | "no-var-requires": false, 138 | "ordered-imports": false, 139 | "radix": false, 140 | "trailing-comma": false, 141 | "align": false, 142 | "eofline": false, 143 | "no-consecutive-blank-lines": false, 144 | "space-before-function-paren": false, 145 | "ban-comma-operator": false, 146 | "max-classes-per-file": false, 147 | "member-ordering": false, 148 | "no-angle-bracket-type-assertion": false, 149 | "no-bitwise": false, 150 | "no-namespace": false, 151 | "no-reference": false, 152 | "object-literal-sort-keys": false, 153 | "one-variable-per-declaration": false, 154 | "type-operator-spacing": false, 155 | "no-type-assertion-whitespace": false, 156 | "object-literal-surrounding-space": false, 157 | "no-increment-decrement": false, 158 | "no-in-operator": false, 159 | "no-double-space": false, 160 | "no-unnecessary-type-assertion-2": false, 161 | "no-bom": false, 162 | "boolean-trivia": false, 163 | "debug-assert": false, 164 | "no-unused-expression": false 165 | } 166 | } -------------------------------------------------------------------------------- /demo/rest-api/README.md: -------------------------------------------------------------------------------- 1 | # Nestjs CRUD Restful API Demo 2 | 3 | ## 先决条件 4 | 5 | 安装nodejs、typescript、PostgreSQL数据库 6 | 7 | ## Step 1 准备自己喜欢的开发工具 8 | 9 | 这里我推荐使用 [Visual Studio Code(VS Code)](https://code.visualstudio.com),插件推荐: 10 | 11 | - npm 12 | - TSLint 13 | - Typescript Hero 14 | - Code Runner 15 | - .gitignore Generator 16 | - Settings Sync 17 | - TODO hignlight 18 | 19 | 备注:这些插件的作用,请大家自行查阅,安装自己喜欢的即可。 20 | 21 | ## Step 2 创建项目 22 | 23 | 创建一个名称为 crud-demo 的文件夹,使用 VS Code 打开,并按照例子创建项目骨架和必要的文件 24 | 25 | ```bash 26 | # 安装依赖 27 | $ npm install 28 | ``` 29 | 30 | 其中 `package-lock.json` 文件是安装依赖时自动生成的,不需要手动创建 31 | 32 | ```no-language 33 | src 34 | |-- app.controller.ts // 应用程序控制器 35 | |-- app.service.ts // 应用程序业务逻辑 36 | |-- app.module.ts // 应用程序根模块 37 | |-- main.ts // 应用程序入口文件 38 | nodemon-debug.json // nodemon `debug` 模式配置文件 39 | nodemon.json // nodemon 配置文件 40 | package.json // 定义了这个项目所需要的各种模块,以及项目的配置信息 41 | package-lock.json // 各种模块的版本锁文件,用于后续加速安装依赖 42 | tsconfig.json // 文件中指定了用来编译这个项目的根文件和编译选项 43 | tslint.json // ts 语法检查配置文件 44 | ``` 45 | 46 | ## Step 3 创建 Hello World 47 | 48 | `app.controller.ts` 49 | 50 | ```typescript 51 | import { Controller, Get, Inject } from '@nestjs/common'; 52 | 53 | import { AppService } from './app.service'; 54 | 55 | /** 56 | * 应用程序控制器,@Controller() 可以指定参数,用于定义类的父路由,如 @Controller("cat"),此时这个类的所有父路由就会成为 /cat 57 | * 58 | * 被 @Controller() 修饰的类,可以通过其构造函数完成依赖注入,但依赖注入的类必须与当前类属于同一个模块 59 | */ 60 | @Controller() 61 | export class AppController { 62 | 63 | /** 64 | * 构造函数,用于注入这个类的依赖,注入类时,需要使用 @Inject() 修饰符,其参数是被注入的类的类名 65 | * 66 | * 在注入被 @Injectable() 修饰的类时,可以不使用 @Inject() 修饰参数,此时依赖注器入会使用参数的类型完成注入 67 | * 68 | * Tips: 这里我使用 @Inject(AppService) 是为了规范代码风格 69 | */ 70 | constructor( 71 | @Inject(AppService) private readonly appService: AppService, 72 | ) { } 73 | 74 | /** 75 | * @Get() 可以指定参数,用于定义方法路由,如 @Get(":id"),此时这个方法路由就会成为 /cat/:id,即查询指定ID的 Cat 76 | */ 77 | @Get() 78 | async root() { 79 | return this.appService.root(); 80 | } 81 | } 82 | ``` 83 | 84 | `app.service.ts` 85 | 86 | ```typescript 87 | import { Injectable } from '@nestjs/common'; 88 | 89 | /** 90 | * 被 @Injectable() 修饰的类,可以通过其构造函数完成依赖注入,但依赖注入的类必须与当前类属于同一个模块 91 | */ 92 | @Injectable() 93 | export class AppService { 94 | constructor() { } // 构造函数,一般用于处理依赖注入 95 | 96 | async root() { 97 | return 'Hello World!'; 98 | } 99 | } 100 | ``` 101 | 102 | `app.module.ts` 103 | 104 | ```typescript 105 | import { Module } from '@nestjs/common'; 106 | import { APP_INTERCEPTOR } from '@nestjs/core'; 107 | import { TypeOrmModule } from '@nestjs/typeorm'; 108 | import { CatModule } from 'cats/cat.module'; 109 | import { ErrorsInterceptor } from 'common/errors.interceptor'; 110 | 111 | import { AppController } from './app.controller'; 112 | import { AppService } from './app.service'; 113 | 114 | /** 115 | * @Module() 定义一个模块,并管理这个模块的导入集合、控制器集合、提供者集合、导出集合 116 | */ 117 | @Module({ 118 | // TypeOrmModule.forRoot() 默认加载项目根目录下的 ormconfig.json 配置文件用于配置数据库连接 119 | // TypeORM 配置文件详细文档 https://typeorm.io/#/using-ormconfig 120 | imports: [TypeOrmModule.forRoot(), CatModule], // 导入其他模块的集合 121 | controllers: [AppController], // 当前模块的控制器集合 122 | providers: [ 123 | { 124 | provide: APP_INTERCEPTOR, 125 | useClass: ErrorsInterceptor 126 | }, 127 | AppService 128 | ], // 当前模块的提供者集合 129 | exports: [], // 导出当前模块的提供者,用于被其他模块调用 130 | }) 131 | export class AppModule { } 132 | ``` 133 | 134 | `main.ts` 135 | 136 | ```typescript 137 | import { NestFactory } from '@nestjs/core'; 138 | 139 | import { AppModule } from './app.module'; 140 | 141 | async function bootstrap() { 142 | const app = await NestFactory.create(AppModule); // 创建应用程序实例,此时所有被 AppModule 导入的其他模块的所有实例都会被加载 143 | await app.listen(3000); // 使用3000端口监听应用程序 144 | } 145 | 146 | bootstrap(); // 启动应用程序 -> localhost:3000 147 | ``` 148 | 149 | 启动应用程序 `$ npm run start`,打开浏览器输入 `localhost:3000`! 150 | 151 | ## Step 4 创建 CRUD Restful API 152 | 153 | `cat.controller.ts` 154 | 155 | ```typescript 156 | import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common'; 157 | import { Result } from 'common/result.interface'; 158 | 159 | import { Cat } from './cat.entity'; 160 | import { CatService } from './cat.service'; 161 | 162 | @Controller('cat') 163 | export class CatController { 164 | constructor( 165 | @Inject(CatService) private readonly CatService: CatService, 166 | ) { } 167 | 168 | @Post() 169 | async createCat(@Body() Cat: Cat): Promise { 170 | await this.CatService.createCat(Cat); 171 | return { code: 200, message: '创建成功' }; 172 | } 173 | 174 | @Delete(':id') 175 | async deleteCat(@Param('id') id: number): Promise { 176 | await this.CatService.deleteCat(id); 177 | return { code: 200, message: '删除成功' }; 178 | } 179 | 180 | @Put(':id') 181 | async updateCat(@Param('id') id: number, @Body() Cat: Cat): Promise { 182 | await this.CatService.updateCat(id, Cat); 183 | return { code: 200, message: '更新成功' }; 184 | } 185 | 186 | @Get(':id') 187 | async findOneCat(@Param('id') id: number): Promise { 188 | const data = await this.CatService.findOneCat(id); 189 | return { code: 200, message: '查询成功', data }; 190 | } 191 | } 192 | ``` 193 | 194 | `cat.service.ts` 195 | 196 | ```typescript 197 | import { HttpException, Injectable } from '@nestjs/common'; 198 | import { InjectRepository } from '@nestjs/typeorm'; 199 | import { Repository } from 'typeorm'; 200 | 201 | import { Cat } from './cat.entity'; 202 | 203 | @Injectable() 204 | export class CatService { 205 | constructor( 206 | @InjectRepository(Cat) private readonly catRepo: Repository, // 使用泛型注入对应类型的存储库实例 207 | ) { } 208 | 209 | /** 210 | * 创建 211 | * 212 | * @param cat Cat 实体对象 213 | */ 214 | async createCat(cat: Cat): Promise { 215 | /** 216 | * 创建新的实体实例,并将此对象的所有实体属性复制到新实体中。 请注意,它仅复制实体模型中存在的属性。 217 | */ 218 | // this.catRepo.create(cat); 219 | 220 | // 插入数据时,删除 id,以避免请求体内传入 id 221 | delete cat.id; 222 | return this.catRepo.save(cat); 223 | 224 | /** 225 | * 将给定实体插入数据库。与save方法不同,执行原始操作时不包括级联,关系和其他操作。 226 | * 执行快速有效的INSERT操作。不检查数据库中是否存在实体,因此如果插入重复实体,本次操作将失败。 227 | */ 228 | // await this.catRepo.insert(cat); 229 | } 230 | 231 | /** 232 | * 删除 233 | * 234 | * @param id ID 235 | */ 236 | async deleteCat(id: number): Promise { 237 | await this.findOneById(id); 238 | this.catRepo.delete(id); 239 | } 240 | 241 | /** 242 | * 更新 243 | * 244 | * @param id ID 245 | * @param cat Cat 实体对象 246 | */ 247 | async updateCat(id: number, cat: Cat): Promise { 248 | await this.findOneById(id); 249 | // 更新数据时,删除 id,以避免请求体内传入 id 250 | delete cat.id; 251 | this.catRepo.update(id, cat); 252 | } 253 | 254 | /** 255 | * 根据ID查询 256 | * 257 | * @param id ID 258 | */ 259 | async findOneCat(id: number): Promise { 260 | return this.findOneById(id); 261 | } 262 | 263 | /** 264 | * 根据ID查询单个信息,如果不存在则抛出404异常 265 | * @param id ID 266 | */ 267 | private async findOneById(id: number): Promise { 268 | const catInfo = await this.catRepo.findOne(id); 269 | if (!catInfo) { 270 | throw new HttpException(`指定 id=${id} 的猫猫不存在`, 404); 271 | } 272 | return catInfo; 273 | } 274 | } 275 | ``` 276 | 277 | `cat.entity.ts` 278 | 279 | ```typescript 280 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 281 | 282 | @Entity('cat') 283 | export class Cat { 284 | /** 285 | * 自增主键 286 | */ 287 | @PrimaryGeneratedColumn({ 288 | comment: '自增ID' 289 | }) 290 | id: number; 291 | 292 | /** 293 | * 昵称 294 | */ 295 | @Column({ 296 | comment: '昵称' 297 | }) 298 | nickname: string; 299 | 300 | /** 301 | * 品种 302 | */ 303 | @Column({ 304 | comment: '品种' 305 | }) 306 | species: string; 307 | } 308 | ``` 309 | 310 | `cat.module.ts` 311 | 312 | ```typescript 313 | import { Module } from '@nestjs/common'; 314 | import { TypeOrmModule } from '@nestjs/typeorm'; 315 | 316 | import { CatController } from './cat.controller'; 317 | import { Cat } from './cat.entity'; 318 | import { CatService } from './cat.service'; 319 | 320 | @Module({ 321 | imports: [TypeOrmModule.forFeature([Cat])], 322 | controllers: [CatController], 323 | providers: [CatService], 324 | }) 325 | export class CatModule { } 326 | ``` 327 | 328 | 接下来,就可以使用 Postman 测试接口了 -------------------------------------------------------------------------------- /demo/rest-api/nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "exec": "node --inspect-brk -r ts-node/register src/main.ts" 10 | } -------------------------------------------------------------------------------- /demo/rest-api/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 10 | } -------------------------------------------------------------------------------- /demo/rest-api/ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "localhost", 4 | "port": 5432, 5 | "username": "postgres", 6 | "password": "123456", 7 | "database": "test", 8 | "entities": [ 9 | "src/**/**.entity{.ts,.js}" 10 | ], 11 | "synchronize": true, 12 | "logging": true 13 | } -------------------------------------------------------------------------------- /demo/rest-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-rest-api-demo", 3 | "version": "1.0.0", 4 | "description": "This is a nestjs rest api demo.", 5 | "main": "main.ts", 6 | "scripts": { 7 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 8 | "start:watch": "nodemon", 9 | "debug": "node --inspect-brk -r ts-node/register src/main.ts", 10 | "debug:watch": "nodemon --config nodemon-debug.json", 11 | "check": "tslint -p tsconfig.json -c tslint.json", 12 | "fix": "tslint -p tsconfig.json -c tslint.json --fix" 13 | }, 14 | "author": "dzzzzzy", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@nestjs/common": "^6.0.0", 18 | "@nestjs/core": "^6.0.0", 19 | "@nestjs/platform-express": "^6.0.0", 20 | "@nestjs/typeorm": "^7.0.0", 21 | "pg": "^7.9.0", 22 | "reflect-metadata": "^0.1.13", 23 | "rxjs": "^6.4.0", 24 | "typeorm": "^0.2.15", 25 | "typescript": "^3.3.3333" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^11.11.3", 29 | "nodemon": "^1.18.10", 30 | "ts-node": "^8.0.3", 31 | "tsconfig-paths": "^3.8.0", 32 | "tslint": "^5.14.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /demo/rest-api/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Inject } from '@nestjs/common'; 2 | 3 | import { AppService } from './app.service'; 4 | 5 | /** 6 | * 应用程序控制器,@Controller() 可以指定参数,用于定义类的父路由,如 @Controller("cat"),此时这个类的所有父路由就会成为 /cat 7 | * 8 | * 被 @Controller() 修饰的类,可以通过其构造函数完成依赖注入,但依赖注入的类必须与当前类属于同一个模块 9 | */ 10 | @Controller() 11 | export class AppController { 12 | 13 | /** 14 | * 构造函数,用于注入这个类的依赖,注入类时,需要使用 @Inject() 修饰符,其参数是被注入的类的类名 15 | * 16 | * 在注入被 @Injectable() 修饰的类时,可以不使用 @Inject() 修饰参数,此时依赖注器入会使用参数的类型完成注入 17 | * 18 | * Tips: 这里我使用 @Inject(AppService) 是为了规范代码风格 19 | */ 20 | constructor( 21 | @Inject(AppService) private readonly appService: AppService, 22 | ) { } 23 | 24 | /** 25 | * @Get() 可以指定参数,用于定义方法路由,如 @Get(":id"),此时这个方法路由就会成为 /cat/:id,即查询指定ID的 Cat 26 | */ 27 | @Get() 28 | async root() { 29 | return this.appService.root(); 30 | } 31 | } -------------------------------------------------------------------------------- /demo/rest-api/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { APP_INTERCEPTOR } from '@nestjs/core'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | 5 | import { AppController } from './app.controller'; 6 | import { AppService } from './app.service'; 7 | import { CatModule } from './cat/cat.module'; 8 | import { ErrorsInterceptor } from './common/errors.interceptor'; 9 | 10 | /** 11 | * @Module() 定义一个模块,并管理这个模块的导入集合、控制器集合、提供者集合、导出集合 12 | */ 13 | @Module({ 14 | // TypeOrmModule.forRoot() 默认加载项目根目录下的 ormconfig.json 配置文件用于配置数据库连接 15 | // TypeORM 配置文件详细文档 https://typeorm.io/#/using-ormconfig 16 | imports: [TypeOrmModule.forRoot(), CatModule], // 导入其他模块的集合 17 | controllers: [AppController], // 当前模块的控制器集合 18 | providers: [ 19 | { 20 | provide: APP_INTERCEPTOR, 21 | useClass: ErrorsInterceptor 22 | }, 23 | AppService 24 | ], // 当前模块的提供者集合 25 | exports: [], // 导出当前模块的提供者,用于被其他模块调用 26 | }) 27 | export class AppModule { } -------------------------------------------------------------------------------- /demo/rest-api/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | /** 4 | * 被 @Injectable() 修饰的类,可以通过其构造函数完成依赖注入,但依赖注入的类必须与当前类属于同一个模块 5 | */ 6 | @Injectable() 7 | export class AppService { 8 | constructor() { } // 构造函数,一般用于处理依赖注入 9 | 10 | async root() { 11 | return 'Hello World!'; 12 | } 13 | } -------------------------------------------------------------------------------- /demo/rest-api/src/cat/cat.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common'; 2 | 3 | import { Result } from '../common/result.interface'; 4 | import { Cat } from './cat.entity'; 5 | import { CatService } from './cat.service'; 6 | 7 | @Controller('cat') 8 | export class CatController { 9 | constructor( 10 | @Inject(CatService) private readonly catService: CatService, 11 | ) { } 12 | 13 | @Post() 14 | async createCat(@Body() cat: Cat): Promise { 15 | await this.catService.createCat(cat); 16 | return { code: 200, message: '创建成功' }; 17 | } 18 | 19 | @Delete(':id') 20 | async deleteCat(@Param('id') id: number): Promise { 21 | await this.catService.deleteCat(id); 22 | return { code: 200, message: '删除成功' }; 23 | } 24 | 25 | @Put(':id') 26 | async updateCat(@Param('id') id: number, @Body() cat: Cat): Promise { 27 | await this.catService.updateCat(id, cat); 28 | return { code: 200, message: '更新成功' }; 29 | } 30 | 31 | @Get(':id') 32 | async findOneCat(@Param('id') id: number): Promise { 33 | const data = await this.catService.findOneCat(id); 34 | return { code: 200, message: '查询成功', data }; 35 | } 36 | } -------------------------------------------------------------------------------- /demo/rest-api/src/cat/cat.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity('cat') 4 | export class Cat { 5 | /** 6 | * 自增主键 7 | */ 8 | @PrimaryGeneratedColumn({ 9 | comment: '自增ID' 10 | }) 11 | id: number; 12 | 13 | /** 14 | * 昵称 15 | */ 16 | @Column({ 17 | comment: '昵称' 18 | }) 19 | nickname: string; 20 | 21 | /** 22 | * 品种 23 | */ 24 | @Column({ 25 | comment: '品种' 26 | }) 27 | species: string; 28 | } -------------------------------------------------------------------------------- /demo/rest-api/src/cat/cat.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | 4 | import { CatController } from './cat.controller'; 5 | import { Cat } from './cat.entity'; 6 | import { CatService } from './cat.service'; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([Cat])], 10 | controllers: [CatController], 11 | providers: [CatService], 12 | }) 13 | export class CatModule { } -------------------------------------------------------------------------------- /demo/rest-api/src/cat/cat.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | 5 | import { Cat } from './cat.entity'; 6 | 7 | @Injectable() 8 | export class CatService { 9 | constructor( 10 | @InjectRepository(Cat) private readonly catRepo: Repository, // 使用泛型注入对应类型的存储库实例 11 | ) { } 12 | 13 | /** 14 | * 创建 15 | * 16 | * @param cat Cat 实体对象 17 | */ 18 | async createCat(cat: Cat): Promise { 19 | /** 20 | * 创建新的实体实例,并将此对象的所有实体属性复制到新实体中。 请注意,它仅复制实体模型中存在的属性。 21 | */ 22 | // this.catRepo.create(cat); 23 | 24 | // 插入数据时,删除 id,以避免请求体内传入 id 25 | delete cat.id; 26 | return this.catRepo.save(this.catRepo.create(cat)); 27 | 28 | /** 29 | * 将给定实体插入数据库。与save方法不同,执行原始操作时不包括级联,关系和其他操作。 30 | * 执行快速有效的INSERT操作。不检查数据库中是否存在实体,因此如果插入重复实体,本次操作将失败。 31 | */ 32 | // await this.catRepo.insert(cat); 33 | } 34 | 35 | /** 36 | * 删除 37 | * 38 | * @param id ID 39 | */ 40 | async deleteCat(id: number): Promise { 41 | await this.findOneById(id); 42 | this.catRepo.delete(id); 43 | } 44 | 45 | /** 46 | * 更新 47 | * 48 | * @param cat Cat 实体对象 49 | */ 50 | async updateCat(id: number, cat: Cat): Promise { 51 | const existCat = await this.findOneById(id); 52 | // 当传入空数据时,避免覆盖原数据 53 | existCat.nickname = cat && cat.nickname ? cat.nickname : existCat.nickname; 54 | existCat.species = cat && cat.species ? cat.species : existCat.species; 55 | this.catRepo.save(existCat); 56 | } 57 | 58 | /** 59 | * 根据ID查询 60 | * 61 | * @param id ID 62 | */ 63 | async findOneCat(id: number): Promise { 64 | return this.findOneById(id); 65 | } 66 | 67 | /** 68 | * 根据ID查询单个信息,如果不存在则抛出404异常 69 | * @param id ID 70 | */ 71 | private async findOneById(id: number): Promise { 72 | const catInfo = await this.catRepo.findOne(id); 73 | if (!catInfo) { 74 | throw new HttpException(`指定 id=${id} 的猫猫不存在`, 404); 75 | } 76 | return catInfo; 77 | } 78 | } -------------------------------------------------------------------------------- /demo/rest-api/src/common/errors.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, HttpException, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { catchError } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class ErrorsInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | // 异常拦截器,拦截每个请求中的异常,目的是将异常码和异常信息改写为 { code: xxx, message: xxx } 类型 9 | return next.handle().pipe(catchError((error, caught): any => { 10 | if (error instanceof HttpException) { 11 | return Promise.resolve({ 12 | code: error.getStatus(), 13 | message: error.getResponse() 14 | }); 15 | } 16 | return Promise.resolve({ 17 | code: 500, 18 | message: `出现了意外错误:${error.toString()}` 19 | }); 20 | })); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/rest-api/src/common/result.interface.ts: -------------------------------------------------------------------------------- 1 | // 定义通用的API接口返回数据类型 2 | export interface Result { 3 | code: number; 4 | message: string; 5 | data?: any; 6 | } 7 | -------------------------------------------------------------------------------- /demo/rest-api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); // 创建应用程序实例,此时所有被 AppModule 导入的其他模块的所有实例都会被加载 7 | 8 | await app.listen(3000); // 使用3000端口监听应用程序 9 | } 10 | 11 | bootstrap(); // 启动应用程序 -> localhost:3000 -------------------------------------------------------------------------------- /demo/rest-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "baseUrl": "./", 5 | "declaration": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "noImplicitAny": false, 10 | "noLib": false, 11 | "lib": [ 12 | "es2017", 13 | "esnext.asynciterable" 14 | ], 15 | "noUnusedLocals": false, 16 | "removeComments": true, 17 | "strict": false, 18 | "strictPropertyInitialization": false, 19 | "target": "es2017", 20 | "outDir": "dist" 21 | }, 22 | "include": [ 23 | "src/*.ts", 24 | "src/**/*.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "test" 29 | ] 30 | } -------------------------------------------------------------------------------- /demo/rest-api/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": { 7 | "no-unused-expression": true 8 | }, 9 | "rules": { 10 | "array-type": [ 11 | true, 12 | "array" 13 | ], 14 | "ban-types": { 15 | "options": [ 16 | [ 17 | "Object", 18 | "Avoid using the `Object` type. Did you mean `object`?" 19 | ], 20 | [ 21 | "Function", 22 | "Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`." 23 | ], 24 | [ 25 | "Boolean", 26 | "Avoid using the `Boolean` type. Did you mean `boolean`?" 27 | ], 28 | [ 29 | "Number", 30 | "Avoid using the `Number` type. Did you mean `number`?" 31 | ], 32 | [ 33 | "String", 34 | "Avoid using the `String` type. Did you mean `string`?" 35 | ] 36 | ] 37 | }, 38 | "class-name": true, 39 | "comment-format": [ 40 | true, 41 | "check-space" 42 | ], 43 | "curly": [ 44 | true, 45 | "ignore-same-line" 46 | ], 47 | "indent": [ 48 | true, 49 | "spaces", 50 | 4 51 | ], 52 | "max-line-length": [ 53 | true, 54 | 150 55 | ], 56 | "quotemark": [ 57 | true, 58 | "single" 59 | ], 60 | "semicolon": [ 61 | true, 62 | "always" 63 | ], 64 | "interface-name": [ 65 | false 66 | ], 67 | "interface-over-type-literal": true, 68 | "jsdoc-format": true, 69 | "linebreak-style": false, 70 | "next-line": false, 71 | "no-inferrable-types": true, 72 | "no-internal-module": true, 73 | "no-null-keyword": true, 74 | "no-switch-case-fall-through": true, 75 | "no-trailing-whitespace": [ 76 | true, 77 | "ignore-template-strings" 78 | ], 79 | "no-var-keyword": true, 80 | "object-literal-shorthand": true, 81 | "one-line": [ 82 | true, 83 | "check-open-brace", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "prefer-conditional-expression": [ 88 | true, 89 | "check-else-if" 90 | ], 91 | "prefer-for-of": true, 92 | "space-within-parens": true, 93 | "triple-equals": true, 94 | "typedef-whitespace": [ 95 | true, 96 | { 97 | "call-signature": "nospace", 98 | "index-signature": "nospace", 99 | "parameter": "nospace", 100 | "property-declaration": "nospace", 101 | "variable-declaration": "nospace" 102 | } 103 | ], 104 | "whitespace": [ 105 | true, 106 | "check-branch", 107 | "check-decl", 108 | "check-operator", 109 | "check-separator", 110 | "check-type" 111 | ], 112 | "no-implicit-dependencies": false, 113 | "object-literal-key-quotes": [ 114 | true, 115 | "consistent-as-needed" 116 | ], 117 | "variable-name": [ 118 | true, 119 | "ban-keywords", 120 | "check-format", 121 | "allow-leading-underscore", 122 | "allow-pascal-case" 123 | ], 124 | "arrow-parens": false, 125 | "arrow-return-shorthand": true, 126 | "forin": false, 127 | "member-access": false, 128 | "no-conditional-assignment": false, 129 | "no-console": false, 130 | "no-debugger": false, 131 | "no-empty": false, 132 | "no-empty-interface": false, 133 | "no-eval": false, 134 | "no-object-literal-type-assertion": false, 135 | "no-shadowed-variable": false, 136 | "no-submodule-imports": false, 137 | "no-var-requires": false, 138 | "ordered-imports": false, 139 | "radix": false, 140 | "trailing-comma": false, 141 | "align": false, 142 | "eofline": false, 143 | "no-consecutive-blank-lines": false, 144 | "space-before-function-paren": false, 145 | "ban-comma-operator": false, 146 | "max-classes-per-file": false, 147 | "member-ordering": false, 148 | "no-angle-bracket-type-assertion": false, 149 | "no-bitwise": false, 150 | "no-namespace": false, 151 | "no-reference": false, 152 | "object-literal-sort-keys": false, 153 | "one-variable-per-declaration": false, 154 | "type-operator-spacing": false, 155 | "no-type-assertion-whitespace": false, 156 | "object-literal-surrounding-space": false, 157 | "no-increment-decrement": false, 158 | "no-in-operator": false, 159 | "no-double-space": false, 160 | "no-unnecessary-type-assertion-2": false, 161 | "no-bom": false, 162 | "boolean-trivia": false, 163 | "debug-assert": false, 164 | "no-unused-expression": false 165 | } 166 | } -------------------------------------------------------------------------------- /docs/controller.md: -------------------------------------------------------------------------------- 1 | # Nest 基础功能 —— Controller 2 | 3 | 什么是 `Controller`?语义化翻译就是 **控制器**,它负责处理传入的请求并将响应结果返回给客户端。 4 | 5 | 控制器的目的是接收应用程序的特定请求。其 **路由** 机制控制哪个控制器接收哪些请求。通常,每个控制器都有多个路由,不同的路由可以执行不同的操作。 6 | 7 | ## 如何将类定义为控制器? 8 | 9 | 在类声明上,定义 `@Controller()` 装饰器,即可将该类定义为控制器,在 `Nest` 中,几乎所有的装饰器都是以方法形式存在的,我们通过查看 `@Controller()` 装饰器的源码: 10 | 11 | ```typescript 12 | export function Controller(prefix?: string): ClassDecorator { 13 | const path = isUndefined(prefix) ? '/' : prefix; 14 | return (target: object) => { 15 | Reflect.defineMetadata(PATH_METADATA, path, target); 16 | }; 17 | } 18 | ``` 19 | 20 | 可以看出,控制器装饰器有一个可选的方法参数,该参数默认值为 `/`,其作用就是定义当前控制器类下所有路由方法的前缀,这样以来,就可以避免我们在定义路由时出现多余的相同前缀。 21 | 22 | 现在,让我们定义一个前缀为 `cats` 的控制器: 23 | 24 | ```typescript 25 | import { Controller } from '@nestjs/common'; 26 | 27 | @Controller('cats') 28 | export class CatsController { } 29 | ``` 30 | 31 | ## 如何在类的方法上定义路由? 32 | 33 | 在类的方法声明上,定义 `@Get()`、`@Post()`、`@Put()`、`@Patch()`、`@Delete()`、`@Options()`、`@Head()`、`@All()`。这些装饰器都表示各自的 **HTTP** 请求方法。 34 | 35 | 现在,让我们在上述的 `CatsController` 类中定义实际开发中常用的几种路由映射: 36 | 37 | ```typescript 38 | import { Controller, Get, Post, Body, Put, Param, Delete } from '@nestjs/common'; 39 | 40 | @Controller('cats') 41 | export class CatsController { 42 | @Post() 43 | async create(@Body() createCatDto: CreateCatDto) { 44 | return 'This action adds a new cat'; 45 | } 46 | 47 | @Delete(':id') 48 | async remove(@Param('id') id) { 49 | return `This action removes a #${id} cat`; 50 | } 51 | 52 | @Put(':id') 53 | async update(@Param('id') id, @Body() updateCatDto: UpdateCatDto) { 54 | return `This action updates a #${id} cat`; 55 | } 56 | 57 | @Get(':id') 58 | async findOne(@Param('id') id) { 59 | return `This action returns a #${id} cat`; 60 | } 61 | 62 | @Get() 63 | async findAll(@Query() query) { 64 | return `This action returns all cats (limit: ${query.limit} items)`; 65 | } 66 | } 67 | 68 | // createCatDto 和 updateCatDto 可以使用类来定义其结构: 69 | export class CreateCatDto { 70 | id: number; 71 | name: string; 72 | } 73 | 74 | export class UpdateCatDto { 75 | name: string; 76 | } 77 | ``` 78 | 79 | 在定义路由时,方法参数中,我们使用到了 `@Body()`、`@Query()`、`@Param()` 这样的参数装饰器,他们是什么意思呢?用过 `express` 的同学都会经常接触到:`req`、`res`、`next`、`req.body`、`req.query`、`req.params`等等,诸如此类的特性,下面我们列举出,在 **Nest** 框架中,这些装饰器与 `express` 中的使用方法是如何对应的: 80 | 81 | | Nest | Express | 82 | | :----------------------- | :------------------------------- | 83 | | @Request() | req | 84 | | @Response() | res | 85 | | @Next() | next | 86 | | @Session() | req.session | 87 | | @Param(param?: string) | req.params / req.params[param] | 88 | | @Body(param?: string) | req.body / req.body[param] | 89 | | @Query(param?: string) | req.query / req.query[param] | 90 | | @Headers(param?: string) | req.headers / req.headers[param] | 91 | 92 | > Tips:Nest 底层默认使用了 `express` 作为 web 层的框架 93 | > 94 | > 注意:如果在方法参数中定义了 @Res() 或 @Next(),此时该方法的 return 语句会被阻塞,因为 return 形式的返回是 nest 标准形式,而一但使用了上述两个装饰器后,nest 不会在用标准 95 | 形式返回数据,此时必须使用 res.send / res.end / res.json 等,这种 `express` 特定的写法返回数据。 96 | 97 | ### 通配符路由 98 | 99 | Nest 也支持基于模式的路由。例如,星号用作通配符,将匹配任何字符组合。示例: 100 | 101 | ```typescript 102 | @Get('ab*cd') 103 | async findAll() { 104 | return 'This route uses a wildcard'; 105 | } 106 | ``` 107 | 108 | 以上路由路径将匹配ABCD、ab _ CD、abecd等。字符 `?`、`+`、`*`、`()` 是正则表达式对应项的子集。连字符(`-`)和点(`.`)由字符串路径按字面解释。 109 | 110 | ### 状态码(Status code) 111 | 112 | 如上所述,默认情况下,除 POST 请求的状态码是 **201** 以外,其余请求的状态码总是 **200**,但我们可以通过在方法声明上添加 `@HttpCode(...)` 装饰器来改变默认的状态码: 113 | 114 | ```typescript 115 | @Post() 116 | @HttpCode(204) 117 | async create() { 118 | return 'This action adds a new cat'; 119 | } 120 | ``` 121 | 122 | ### 响应头(Headers) 123 | 124 | 若要指定自定义响应头,可以使用 `@Header()` 装饰器: 125 | 126 | ```typescript 127 | @Post() 128 | @Header('Cache-Control', 'none') 129 | async create() { 130 | return 'This action adds a new cat'; 131 | } 132 | ``` 133 | 134 | > 下一节:Nest 基础功能 —— [Provider](./provider.md) 135 | -------------------------------------------------------------------------------- /docs/exception-filter.md: -------------------------------------------------------------------------------- 1 | # Nest 高级功能 —— Exception filter 2 | 3 | Nest 内置异常层负责处理整个应用程序中抛出的所有异常。当捕获到未处理的异常时,用户最终将收到适当的友好响应。 4 | 5 | 每个出现的异常都由全局异常过滤器处理,当无法识别时(既不是HttpException,也不是从HttpException继承的类),用户会收到以下JSON响应: 6 | 7 | ```json 8 | { 9 | "statusCode": 500, 10 | "message": "Internal server error" 11 | } 12 | ``` 13 | 14 | ## 基础异常 15 | 16 | 在讲异常过滤器前,我们先熟悉框架的基础异常类:`HttpException`,`HttpException` 类来自于 `@nestjs/common`。正如你所想的,当程序抛出一个 `HttpException` 对象时,它会被异常处理程序捕获,然后转换成相关的 **JSON** 响应。 17 | 18 | 如何在控制器层抛出一个 `HttpException` 呢? 19 | 20 | ```typescript 21 | @Post() 22 | async create(@Body() createCatDto: CreateCatDto) { 23 | throw new HttpException('Forbidden', HttpStatus.FORBIDDEN); 24 | } 25 | ``` 26 | 27 | 此时,客户端会接收到如下 **JSON** 相应: 28 | 29 | ```json 30 | { 31 | "statusCode": 403, 32 | "message": "Forbidden" 33 | } 34 | ``` 35 | 36 | `HttpException` 类的构造器中第一个参数可以是 `string` 或 `object`,当你将异常改写成如下时: 37 | 38 | ```typescript 39 | @Post() 40 | async create(@Body() createCatDto: CreateCatDto) { 41 | throw new HttpException({ 42 | status: HttpStatus.FORBIDDEN, 43 | error: 'This is a custom message', 44 | }, 403); 45 | } 46 | ``` 47 | 48 | 客户端会收到: 49 | 50 | ```json 51 | { 52 | "status": 403, 53 | "error": "This is a custom message" 54 | } 55 | ``` 56 | 57 | 这种通常用来做自定义的异常消息返回 58 | 59 | ## 异常等级/分类 60 | 61 | 在进行 web 开发时,通常会对异常进行等级或分类处理,nest 提供了开箱即用的常用异常类,在使用时,只需实例化相应的异常类: 62 | 63 | - BadRequestException 64 | - UnauthorizedException 65 | - NotFoundException 66 | - ForbiddenException 67 | - NotAcceptableException 68 | - RequestTimeoutException 69 | - ConflictException 70 | - GoneException 71 | - PayloadTooLargeException 72 | - UnsupportedMediaTypeException 73 | - UnprocessableEntityException 74 | - InternalServerErrorException 75 | - NotImplementedException 76 | - BadGatewayException 77 | - ServiceUnavailableException 78 | - GatewayTimeoutException 79 | 80 | 如果上述异常类都无法满足我们的业务需求,此时,我们只需要继承 `HttpException` 类,来完成异常类的扩展: 81 | 82 | ```typescript 83 | export class CustomException extends HttpException { 84 | constructor() { 85 | super('custom message', 409); 86 | } 87 | } 88 | ``` 89 | 90 | ## 异常过滤器 91 | 92 | 基础的异常处理程序满足了大多数异常需求,但是有时我们想完全控制异常处理程序,让其以我们期望的形式去工作。例如,增加一些日志,或是依据不同条件返回不同的 **JSON** 结构。此时,nest 的异常过滤器就是我们所期待的这种程序。当我们想改变 `HttpException` 的异常结构时,我们只需要实现 `ExceptionFilter` 接口: 93 | 94 | ```typescript 95 | import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common'; 96 | import { HttpException } from '@nestjs/common'; 97 | 98 | @Catch(HttpException) 99 | export class HttpExceptionFilter implements ExceptionFilter { 100 | catch(exception: HttpException, host: ArgumentsHost) { 101 | const ctx = host.switchToHttp(); // 获取请求上下文 102 | const response = ctx.getResponse(); // 在请求上下文中获取 response 对象 103 | const request = ctx.getRequest(); // 在请求上下文中获取 request 对象 104 | const status = exception.getStatus(); // 获取异常的状态码 105 | 106 | response 107 | .status(status) 108 | .json({ 109 | statusCode: status, 110 | timestamp: new Date().toISOString(), 111 | path: request.url, 112 | }); 113 | } 114 | } 115 | ``` 116 | 117 | 在控制器的某个方法中使用上述定义的异常过滤器: 118 | 119 | ```typescript 120 | @Post() 121 | @UseFilters(HttpExceptionFilter) 122 | async create(@Body() createCatDto: CreateCatDto) { 123 | throw new ForbiddenException(); 124 | } 125 | ``` 126 | 127 | 异常过滤器有如下几种级别: 128 | 129 | - 控制器类级别:在 Controller 类上使用 @UseFilters(HttpExceptionFilter) 130 | - 控制器方法级别:在 Controller 方法上使用 @UseFilters(HttpExceptionFilter) 131 | - 全局级别 132 | 133 | 其中全局级别的异常过滤器有两种使用方法: 134 | 135 | 方法一: 136 | 137 | ```typescript 138 | async function bootstrap() { 139 | const app = await NestFactory.create(ApplicationModule); 140 | app.useGlobalFilters(new HttpExceptionFilter()); 141 | await app.listen(3000); 142 | } 143 | bootstrap(); 144 | ``` 145 | 146 | 方法二: 147 | 148 | ```typescript 149 | import { Module } from '@nestjs/common'; 150 | import { APP_FILTER } from '@nestjs/core'; 151 | 152 | @Module({ 153 | providers: [ 154 | { 155 | provide: APP_FILTER, 156 | useClass: HttpExceptionFilter, 157 | }, 158 | ], 159 | }) 160 | export class ApplicationModule {} 161 | ``` 162 | 163 | 当定义的异常过滤器的构造函数中有依赖注入时,方法一不适用! 164 | 165 | ## 捕获所有异常 166 | 167 | 当你想使用异常过滤器捕获所有异常时,只需要将上文的 `@Catch(HttpException)` 装饰器改写为 `@Catch()` 即可 -------------------------------------------------------------------------------- /docs/middleware.md: -------------------------------------------------------------------------------- 1 | # Nest 高级功能 —— Middleware 2 | 3 | Middleware 即中间件,他是请求发出者和路由处理器之间的桥梁,可以透明的、轻松的访问请求和响应对象。在 Nest 中,中间件可以用多个,他们之间使用 `next()` 方法作为连接,连接后的所有中间件将在整个**请求-响应**周期内通过 `next()` 依次执行。 4 | 5 | > 注:默认情况下,Nest 中间件等同于 `Express` 中间件。 6 | 7 | 以下是从 Express 官方文档中复制的中间件功能列表: 8 | 9 | - 执行任何代码。 10 | - 对请求和响应对象进行更改。 11 | - 结束请求-响应周期。 12 | - 调用堆栈中的下一个中间件函数。 13 | - 如果当前中间件功能没有结束请求-响应周期,它必须调用 `next()` 将控制权传递给下一个中间件功能。否则,请求将被搁置。 14 | 15 | ## Nest 中间件预览 16 | 17 | Nest 中间件可以是一个函数,也可以是一个带有 `@Injectable()` 装饰器的类,且该类应该实现 `NestMiddleware` 接口,而函数没有任何特殊要求。如下是一个日志中间件的简单示例: 18 | 19 | ```typescript 20 | import { Injectable, NestMiddleware } from '@nestjs/common'; 21 | import { Request, Response } from 'express'; 22 | 23 | @Injectable() 24 | export class LoggerMiddleware implements NestMiddleware { 25 | use(req: Request, res: Response, next: Function) { 26 | console.log('Request...'); 27 | next(); 28 | } 29 | } 30 | ``` 31 | 32 | ## 中间件中的依赖注入 33 | 34 | 谈到中间件,也不例外。与提供者(Provider)和控制器(Controller)一样,他能够通过构造函数注入属于同一模块的依赖项: 35 | 36 | ```typescript 37 | import { Injectable, Inject, NestMiddleware } from '@nestjs/common'; 38 | import { Request, Response } from 'express'; 39 | 40 | @Injectable() 41 | export class SomeMiddleware implements NestMiddleware { 42 | constructor(@Inject(SomeService) private readonly someService: SomeService) {} 43 | 44 | use(req: Request, res: Response, next: Function) { 45 | // do some logic... 46 | this.someService.method(); 47 | 48 | console.log('Request...'); 49 | next(); 50 | } 51 | } 52 | ``` 53 | 54 | ## 如何使用中间件? 55 | 56 | 既然中间件是请求发出者和路由处理器之间的桥梁,那么他就应该在一个模块的入口,即 `XXXModule` 类中被使用。在 Nest 中,我们只需要在模块类中实现 `NestModule` 接口: 57 | 58 | ```typescript 59 | import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; 60 | import { LoggerMiddleware } from './common/middlewares/logger.middleware'; 61 | import { CatsModule } from './cats/cats.module'; 62 | 63 | @Module({ 64 | imports: [CatsModule], 65 | }) 66 | export class ApplicationModule implements NestModule { 67 | configure(consumer: MiddlewareConsumer) { 68 | consumer 69 | .apply(LoggerMiddleware) 70 | .forRoutes('cats'); 71 | } 72 | } 73 | ``` 74 | 75 | 在上面的例子中,我们为 `/cats` 路由处理器(`@CatsController('/cats')`)设置了日志中间件。 76 | 如果只需要给 `/cats` 路由中的某几个请求方法设置这个中间件,那只需要改变一下 `forRoutes()` 方法中的参数即可:`forRoutes({ path: 'cats', method: RequestMethod.GET })`,此时,只有 `GET` 请求才会被中间件拦截。 77 | 78 | 当应用程序越来越复杂时,路由也会随之增加,这个时候使用中间件,可能会存在很多 `forRoutes()` 的情况。基于此,Nest 提供了路由通配符的功能(与 `Controller` 中的路由通配符一样)。示例: 79 | 80 | ```typescript 81 | forRoutes({ path: 'ab*cd', method: RequestMethod.ALL }) 82 | ``` 83 | 84 | 除此之外,`forRoutes()` 方法中还可以传入一个控制器类,如:`forRoutes(CatsController)`,他会将 `CatsController` 中的所有路由拦截并使用中间件。如果需要传入多个控制器类,只需要使用 `,` 分割,如: `forRoutes(CatsController, UserController)`。 85 | 86 | 不仅如此,`apply()` 方法同样可以传入一个或多个(用 `,` 分割)中间件,如:`apply(LoggerMiddleware, OtherMiddleware)`。这里可以同时传入类或函数中间件。 87 | 88 | 当你想排除一个控制器类中的某些路由不使用中间件时,使用 `exclude()` 方法即可,如: 89 | 90 | ```typescript 91 | import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common'; 92 | import { LoggerMiddleware } from './common/middlewares/logger.middleware'; 93 | import { CatsModule } from './cats/cats.module'; 94 | 95 | @Module({ 96 | imports: [CatsModule], 97 | }) 98 | export class ApplicationModule implements NestModule { 99 | configure(consumer: MiddlewareConsumer) { 100 | consumer 101 | .apply(LoggerMiddleware) 102 | .exclude( 103 | { path: 'cats', method: RequestMethod.GET }, 104 | { path: 'cats', method: RequestMethod.POST }, 105 | ) 106 | .forRoutes(CatsController); 107 | } 108 | } 109 | ``` 110 | 111 | ## 函数中间件 112 | 113 | Nest 中的中间件可以是类,也可以是一个函数,上述都在讲关于类的中间件,这里使用函数来声明一个中间件: 114 | 115 | ```typescript 116 | export function logger(req, res, next) { 117 | console.log(`Request...`); 118 | next(); 119 | }; 120 | ``` 121 | 122 | 然后,在模块中使用即可: 123 | 124 | ```typescript 125 | import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; 126 | import { logger } from './common/middlewares/logger.middleware'; 127 | import { CatsModule } from './cats/cats.module'; 128 | import { CatsController } from './cats/cats.controller'; 129 | 130 | @Module({ 131 | imports: [CatsModule], 132 | }) 133 | export class ApplicationModule implements NestModule { 134 | configure(consumer: MiddlewareConsumer) { 135 | consumer 136 | .apply(logger) 137 | .forRoutes(CatsController); 138 | } 139 | } 140 | ``` 141 | 142 | ## 全局中间件 143 | 144 | 为了将中间件一次绑定到每个注册的路由,我们可以使用 `INestApplication` 实例中的 `use()` 方法: 145 | 146 | ```typescript 147 | const app = await NestFactory.create(ApplicationModule); 148 | // 这里必须使用函数中间件 149 | app.use(logger); 150 | await app.listen(3000); 151 | ``` 152 | -------------------------------------------------------------------------------- /docs/module.md: -------------------------------------------------------------------------------- 1 | # Nest 基础功能 —— Module 2 | 3 | Nest 主要特性中的模块化开发,就源自与此。Nest 使用 `Module` 来组织应用程序结构,每个应用程序至少有一个模块,即根模块。根模块是 Nest 开始排列应用程序树的地方。事实上,根模块可能是应用程序中唯一的模块,尤其是当应用程序很小时。然而,对于大型应用来说,这是没有意义的。在大多数情况下,您将有很多模块,每个模块都有一组与其密切相关的功能。 4 | 5 | ## Module 有什么实际作用? 6 | 7 | 在上两节(`Controller` 和 `Provider`)中,我们学到了如何在 Nest 中定义并编写 **控制器** 和 **提供者**,本节,我们使用 `Module` 来组织这些 `Controller` 和 `Provider`,为他们在 **同模块范围内** 建立依赖的“桥梁”。 8 | 9 | 现在,让我们编写一个简单的模块,用来组织 `CatsController` 和 `CatsService`: 10 | 11 | ```typescript 12 | import { Module } from '@nestjs/common'; 13 | import { CatsController } from './cats.controller'; 14 | import { CatsService } from './cats.service'; 15 | 16 | @Module({ 17 | controllers: [CatsController], 18 | providers: [CatsService], 19 | }) 20 | export class CatsModule {} 21 | ``` 22 | 23 | 只有这样,Nest 才可以在 `CatsController` 中通过其构造函数,依赖注入 `CatsService`。 24 | 25 | ## 关于 @Module() 装饰器 26 | 27 | `@Module()` 装饰器用来将一个类定义为 Nest 应用程序中的一个模块,在 `Controller` 中,我们了解到 Nest 中的几乎所有装饰器都是一个函数,`@Module()` 也不例外,他的参数可以传入一个对象,用来描述这个模块拥有哪些 **控制器** 和 **提供者**,并且告诉 Nest `IOC` 容器这个模块导入(`imports`)了哪些其他模块、导出(`exports`)了他自己的哪些提供者。 28 | 29 | 模块定义的完整示例: 30 | 31 | ```typescript 32 | import { Module } from '@nestjs/common'; 33 | import { CatsController } from './cats.controller'; 34 | import { CatsService } from './cats.service'; 35 | 36 | @Module({ 37 | imports: [], 38 | controllers: [CatsController], 39 | providers: [CatsService], 40 | exports: [CatsService] 41 | }) 42 | export class CatsModule { } 43 | ``` 44 | 45 | ## 理解 Module 是如何管理依赖注入 46 | 47 | 在 Nest 中,一个模块对依赖注入有着至关重要的作用,在上述例子中,如果我们将 `providers` 数组中的 `CatsService` 去掉,在启动 Nest 程序时,他会告诉你类似如下的错误: 48 | 49 | ```none 50 | [ExceptionHandler] Nest can't resolve dependencies of the CatsController (?). Please verify whether [0] argument is available in the current context. 51 | ``` 52 | 53 | 这个错误的意思是说,Nest 不能处理 `CatsController` 中的依赖关系,即他无法自动的将 `CatsController` 构造器中的 **第1个** 依赖关系注入到 IOC 容器中。而且他还指明,请检查在当前上下文中,`CatsController` 构造器的 **第1个** 参数是否可见。 54 | 55 | 这里的当前上下文指的就是 `CatsModule`,因为 `CatsController` 要依赖 `CatsService` 来完成其功能,但是在当前上下文又找不到 `CatsService` 这个实例(对象),所以 Nest 在启动的时候才会出现这种错误。 56 | 57 | 通俗的讲,如果要在 `controllers` 中的类使用当前模块的其他提供者(`Provider`),那么就必须将其增加到当前模块的 `providers` 中去。 58 | 59 | 上述只是说明了在一个模块中,如何让 `Module` 去管理 `controllers` 和 `providers` 的依赖关系。但是,如果当前模块的 `Controller` 和 `Provider` 需要注入其他模块中的 `Provider` 呢?如果当前模块又需要给其他模块提供他的 `Provider` 呢? 60 | 61 | 这里就要用到 `@Module()` 装饰器中的 `imports` 和 `exports` 了,`imports` 告诉当前模块的 `Controller` 和 `Provider` 注入的非当前模块的 `Provider` 来自于哪个模块,而 `exports` 告诉当前模块要将他的哪些 `Provider` 提供给其他模块。 62 | 63 | > 注意:如果要在当前模块使用其他模块的提供者,那么就必须在被导入的其他模块中,将该提供者放入 `exports` 数组中去。 64 | 65 | 让我们通过一个简单的例子来理解这个看似复杂的依赖关系: 66 | 67 | ```typescript 68 | // 以下仅提供简化后的代码用于理解模块中的依赖注入 69 | 70 | @Module({ 71 | imports: [FishModule], 72 | controllers: [CatsController], 73 | providers: [CatsService], 74 | exports: [] 75 | }) 76 | export class CatsModule { } 77 | 78 | @Module({ 79 | imports: [], 80 | controllers: [FishController], 81 | providers: [FishService], 82 | exports: [FishService] 83 | }) 84 | export class FishModule { } 85 | ``` 86 | 87 | 在上述例子中,`CatsModule` 中的 `CatsService` 要使用 `FishModule` 中的 `FinshService`,只需要在 `CatsModule` 中导入 `FishModule` 并且在 `FishModule` 导出 `FishService` 即可。 88 | 89 | > 注意:在当前模块 imports 中导入的模块,相当于将被导入模块的导出提供者注入到当前模块范围内的 IOC 容器中,以便当前模块的控制器和提供者在其构造器中依赖注入他。 90 | > 91 | > 在 Nest 中,默认情况下,模块是单例的,因此我们可以在不同模块间共用相同的实例。 92 | 93 | ### 模块的重导出 94 | 95 | 什么是模块的重导出?如果要将当前模块导入的模块分享给其他模块,那只需要将当前模块导入的模块放到其 `exports` 数组中即可。这种形式的导出,就叫模块的重导出。示例: 96 | 97 | ```typescript 98 | @Module({ 99 | imports: [CommonModule], 100 | exports: [CommonModule] 101 | }) 102 | export class CoreModule {} 103 | ``` 104 | 105 | ### 能否在一个被定义为模块的类中使用依赖注入? 106 | 107 | 答案是肯定的,很多情况下,比如出于配置的目的,我们需要在模块类中注入某些提供者并且使用他们的公共方法时,Nest 是允许在模块类中注入提供者的。这些提供者必须来自于当前模块范围内,也就是说,可以使用 `@Module()` 装饰器中 `providers` 所提供的任何提供者,也可以使用 `imports` 中导入的模块所导出的提供者。 108 | 109 | 但是!在一个模块类中,是不能导入一个出现循环依赖的提供者的。 110 | 111 | > Tips: 本节暂不进行循环依赖的讲解 112 | 113 | ## 全局模块 114 | 115 | 如果你必须在很多地方都导入相同的模块,这会出现大量的冗余。但是 Nest 将提供者封装在模块范围内,如果不导入模块,就无法在其他地方使用他们导出的提供者。但是有时候,你可能只是想提供一组随时可用的提供者,例如:`helpers`、`database connection` 等等。针对这种特殊情况,Nest 提供了一个很强大的功能 —— **全局模块**,全局模块一旦被导入到根模块,在其他所有模块中即可轻松的使用这个全局模块导出的提供者,而且也不用在其他模块导入这个全局模块。 116 | 117 | 将一个模块定义为全局模块,只需要在类上额外增加一个装饰器 `@Global()` 即可,示例: 118 | 119 | ```typescript 120 | import { Module, Global } from '@nestjs/common'; 121 | 122 | @Global() 123 | @Module({ 124 | imports: [], 125 | controllers: [], 126 | providers: [], 127 | exports: [] 128 | }) 129 | export class CatsModule {} 130 | ``` 131 | 132 | > 注意:Nest 中只能定义一个全局模块! 133 | > 134 | > 将所有东西都放在全局模块内是一个不好的决定,全局模块只是用于减少必要的文件数量,`imports` 仍然是使模块 API 透明的最佳方式。 135 | 136 | ## 动态模块 137 | 138 | Nest 模块系统具有一个称为动态模块的特性。他能够让我们创建可定制的模块,当导入模块并向其传入某些选项参数,这个模块根据这些选项参数来动态的创建不同特性的模块,这种通过导入时传入参数并动态创建模块的特性称为 **动态模块**。 139 | 140 | 下面以一个数据库模块来演示动态模块的使用: 141 | 142 | ```typescript 143 | import { Module, DynamicModule } from '@nestjs/common'; 144 | import { createDatabaseProviders } from './database.providers'; 145 | import { Connection } from './connection.provider'; 146 | 147 | @Module({ 148 | providers: [Connection], 149 | }) 150 | export class DatabaseModule { 151 | static forRoot(entities = [], options?): DynamicModule { 152 | const providers = createDatabaseProviders(options, entities); 153 | return { 154 | module: DatabaseModule, 155 | providers: providers, 156 | exports: providers, 157 | }; 158 | } 159 | } 160 | ``` 161 | 162 | 默认情况下,该模块定义了 `Connection` 提供者,但是根据传递的 options(选项)和 entities(实体),他还导出了提供者,例如存储库。事实上,动态模块扩展了基本模块元数据。当我们需要动态注册提供者时,这一重要功能非常有用。然后可以通过以下方式导入数据库模块: 163 | 164 | ```typescript 165 | import { Module } from '@nestjs/common'; 166 | import { DatabaseModule } from './database/database.module'; 167 | import { User } from './users/entities/user.entity'; 168 | 169 | @Module({ 170 | imports: [ 171 | DatabaseModule.forRoot([User]), 172 | ], 173 | }) 174 | export class ApplicationModule {} 175 | ``` 176 | 177 | 如果需要将这个动态模块导出时,可以省略函数调用部分: 178 | 179 | ```typescript 180 | import { Module } from '@nestjs/common'; 181 | import { DatabaseModule } from './database/database.module'; 182 | import { User } from './users/entities/user.entity'; 183 | 184 | @Module({ 185 | imports: [ 186 | DatabaseModule.forRoot([User]), 187 | ], 188 | exports: [DatabaseModule] 189 | }) 190 | export class ApplicationModule {} 191 | ``` 192 | 193 | > 下一节:Nest 基础功能 —— [NestFactory](./nest-factory.md) 194 | -------------------------------------------------------------------------------- /docs/nest-factory.md: -------------------------------------------------------------------------------- 1 | # Nest 基础功能 —— NestFactory 2 | 3 | 在前面的学习中,我们构建了 `CatsController` 和 `CatsService`,现在让我们用一个应用程序根 `Module` 来管理这两个实例: 4 | 5 | ```typescript 6 | @Module({ 7 | controllers: [CatsController], 8 | providers: [CatsService] 9 | }) 10 | export class AppModule { } 11 | ``` 12 | 13 | 有了应用程序根模块,我们就可以使用 `NestFactory` 来创建一个 Nest 应用了,下面先来看一个简单的例子: 14 | 15 | ```typescript 16 | import { NestFactory } from '@nestjs/core'; 17 | import { AppModule } from './app.module'; 18 | 19 | // 声明一个异步的引导程序函数 20 | async function bootstrap() { 21 | // 使用 NestFactory 创建一个根模块为 AppModule 的 Nest app 22 | const app = await NestFactory.create(AppModule); 23 | // 将这个 Nest app 监听本地的 3000 端口,即:http://localhost:3000 24 | await app.listen(3000); 25 | } 26 | 27 | // 调用引导程序 28 | bootstrap(); 29 | ``` 30 | 31 | 通过上述代码,结合 `Controller` 和 `Provider` 这两小节,我们就可以很轻松的创建一个服务端的 Nest 应用了。 32 | 33 | ## 通过源码深入理解 NestFactory 34 | 35 | > 源码地址:[nest-factory.ts](https://github.com/nestjs/nest/blob/master/packages/core/nest-factory.ts) 36 | 37 | `NestFactory` 是一个被 `new` 创建的 `NestFactoryStatic` 对象,而 `NestFactoryStatic` 对象里的方法正是上文创建 Nest 应用所调用的。 38 | 39 | 下面我们列举 `NestFactory` 对象的 `create` 方法列表: 40 | 41 | ```typescript 42 | public async create(module: any, options?: NestApplicationOptions): Promise; 43 | public async create(module: any, httpAdapter: AbstractHttpAdapter, options?: NestApplicationOptions): Promise; 44 | public async create(module: any, serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions, options?: NestApplicationOptions): Promise { 45 | let [httpServer, appOptions] = this.isHttpServer(serverOrOptions) 46 | ? [serverOrOptions, options] 47 | : [this.createHttpAdapter(), serverOrOptions]; 48 | 49 | const applicationConfig = new ApplicationConfig(); 50 | const container = new NestContainer(applicationConfig); 51 | 52 | this.applyLogger(appOptions); 53 | await this.initialize(module, container, applicationConfig, httpServer); 54 | 55 | const instance = new NestApplication( 56 | container, 57 | httpServer, 58 | applicationConfig, 59 | appOptions, 60 | ); 61 | const target = this.createNestInstance(instance); 62 | return this.createAdapterProxy(target, httpServer); 63 | } 64 | ``` 65 | 66 | 通过 `Typescript` 的函数重载特性,`Nest` 会依据 `create` 方法中传入参数的不同而创建不同类型的 **NestApplication**。比如,默认创建 `NestExpressApplication`,即使用 `express` 作为 web 层的框架,而当在 `create` 方法的第二个参数传入 `new FastifyAdapter()` 时,会创建 `NestFastifyApplication`,这样 web 层就会替换为 `fastify`。 67 | 68 | > 还有两个公共方法,`createMicroservice` 和 `createApplicationContext`,当要创建微服务的 **server** 时使用 `createMicroservice`,当要创建 **应用程序上下文** 时,使用 `createApplicationContext` -------------------------------------------------------------------------------- /docs/provider.md: -------------------------------------------------------------------------------- 1 | # Nest 基础功能 —— Provider 2 | 3 | 什么是 `Provider`? 语义化翻译就是 **提供者**,在 `Nest` 中,除了控制器以外,几乎所有的东西都可以被视为提供者 —— `service`、`repository`、`factory`、`helper` 等等。他们都可以通过构造函数注入依赖关系,也就是说,他们可以彼此创建各种关系。但是事实上,提供者只不过是一个用 `@Injectable()` 装饰的简单类。 4 | 5 | 在上一节中,我们构建了一个简单的 `CatsController`。控制器应处理HTTP请求,并将更复杂的任务委托给 **service**。 6 | 7 | > 注意:由于 `Nest` 能够以更面向对象的方式设计和组织依赖关系,所以强烈建议大家在开发时遵循 **SOLID** 原则。 8 | 9 | ## 如何将类定义为提供者? 10 | 11 | 在类声明上,定义 `@Injectable()` 装饰器,即可将该类定义为提供者。现在,让我们为 `CatsController` 编写一个提供者,他提供了一些方法,用于处理复杂的业务逻辑: 12 | 13 | ```typescript 14 | import { Injectable } from '@nestjs/common'; 15 | 16 | @Injectable() 17 | export class CatsService { 18 | private readonly cats: Cat[] = []; 19 | 20 | async create(createCatDto: CreateCatDto) { 21 | this.cats.push(createCatDto); 22 | } 23 | 24 | async remove(id: number) { 25 | this.cats.splice(cats.indexOf(cats.find(cat => cat.id === id)), 1); 26 | } 27 | 28 | async update(id: number, updateCatDto: UpdateCatDto) { 29 | if(updateCatDto.name) this.cats.find(cat => cat.id === id).name = updateCatDto.name; 30 | } 31 | 32 | async findOne(id: number): Cat { 33 | return this.cats.find(cat => cat.id === id); 34 | } 35 | 36 | async findAll(): Cat[] { 37 | return this.cats; 38 | } 39 | } 40 | 41 | // Cat 是一个接口,用来定义其数据结构 42 | export interface Cat { 43 | id: number; 44 | name: string 45 | } 46 | ``` 47 | 48 | ## 如何让 CatsController 调用 CatsService 中提供的方法? 49 | 50 | 在开始调用 `CatsService` 中提供的方法之前,我们需要了解一下 `DI`(依赖注入) 的概念。(请自行搜索依赖注入的概念哦~) 51 | 52 | 当你了解了依赖注入的概念后,我们现在开始在 `CatsController` 中,使用依赖注入来将 `CatsService` 注入到 `CatsController` 的构造函数中: 53 | 54 | > 在 `Nest` 中,由于 TypeScript 语言的特性,管理依赖项非常容易,因为它们将通过类型来解析,然后传递给控制器的构造函数。 55 | 56 | ```typescript 57 | // 这里提供简化后的代码 58 | @Controller('cats') 59 | export class CatsController { 60 | 61 | constructor( 62 | private readonly catsService: CatsService 63 | ) { } 64 | 65 | @Post() 66 | async create(@Body() createCatDto: CreateCatDto) { 67 | await this.catsService.create(createCatDto); 68 | } 69 | 70 | @Delete(':id') 71 | async remove(@Param('id') id) { 72 | await this.catsService.remove(id); 73 | } 74 | 75 | @Put(':id') 76 | async update(@Param('id') id, @Body() updateCatDto: UpdateCatDto) { 77 | await this.catsService.update(id, updateCatDto); 78 | } 79 | 80 | @Get(':id') 81 | async findOne(@Param('id') id) { 82 | return this.catsService.findOne(id); 83 | } 84 | 85 | @Get() 86 | async findAll() { 87 | return this.catsService.findAll(); 88 | } 89 | } 90 | ``` 91 | 92 | > 下一节:Nest 基础功能 —— [Module](./module.md) 93 | -------------------------------------------------------------------------------- /issues/typeorm/pagination/README.md: -------------------------------------------------------------------------------- 1 | # TypeORM 分页查询问题 2 | 3 | ## 官方教程 4 | 5 | [Using pagination](http://typeorm.io/#/select-query-builder/using-pagination) 6 | 7 | 开发应用程序时,大部分时间都需要分页功能。如果您的应用程序中有分页、页面滑块或无限滚动组件,则使用此选项。 8 | 9 | ```typescript 10 | const users = await getRepository(User) 11 | .createQueryBuilder("user") 12 | .leftJoinAndSelect("user.photos", "photo") 13 | .take(10) 14 | .getManyAndCount(); 15 | ``` 16 | 17 | 这将会查询前10条用户及其照片的数据。 18 | 19 | ```typescript 20 | // getManyAndCount 返回一个长度为2的元组,[0] 是分页后的数据数组, [1] 是所有数据总数 21 | const users = await getRepository(User) 22 | .createQueryBuilder("user") 23 | .leftJoinAndSelect("user.photos", "photo") 24 | .skip(10) 25 | .getManyAndCount(); 26 | ``` 27 | 28 | 这将会查询除了前10条用户以外的所有人及其照片的数据。 29 | 30 | 您可以组合使用他们: 31 | 32 | ```typescript 33 | const users = await getRepository(User) 34 | .createQueryBuilder("user") 35 | .leftJoinAndSelect("user.photos", "photo") 36 | .skip(5) 37 | .take(10) 38 | .getManyAndCount(); 39 | ``` 40 | 41 | 这将会在第6条记录开始查询10条记录,即查询6-16条用户及其照片的数据。 42 | 43 | 注意:**`take` 和 `skip` 可能看起来像 `limit` 和 `offset`,但它们不是。一旦您有更复杂的连接或子查询查询,`limit` 和 `offset` 可能无法正常工作。使用 `take` 和 `skip` 可以防止出现这些问题。** 44 | 45 | ## skip + take 与 offset + limit 的区别 46 | 47 | 当查询中存在连接或子查询时,`skip + take` 的方式总是能正确的返回数据,而 `offset + limit` 返回的数据并不是我们期望的那样。所以查询分页数据时,应该使用 `skip + take`。 48 | 49 | ### 测试 50 | 51 | 使用此目录的测试代码既可 52 | 53 | ```bash 54 | # 在 pagination 目录下,执行 55 | 56 | npm install 57 | 58 | npm run test 59 | ``` 60 | 61 | #### 例子 62 | 63 | ```none 64 | // 查询第 4-8 条数据 65 | userRepo.createQueryBuilder('user').leftJoinAndSelect('user.posts', 'post').offset(3).limit(5).getMany(); 66 | 67 | // 生成的 SQL 68 | query: SELECT "user"."id" AS "user_id", "user"."name" AS "user_name", "post"."id" AS "post_id", "post"."title" AS "post_title", "post"."userId" AS "post_userId" FROM "user" "user" LEFT JOIN "post" "post" ON "post"."userId"="user"."id" LIMIT 5 OFFSET 3 69 | 70 | // 结果集 71 | ┌─────────┬────┬──────────┬─────────────────────────────────────────────────────────────────────────┐ 72 | │ (index) │ id │ name │ posts │ 73 | ├─────────┼────┼──────────┼─────────────────────────────────────────────────────────────────────────┤ 74 | │ 0 │ 2 │ 'user_2' │ '[{"id":3,"title":"user_2_title_1"}]' │ 75 | │ 1 │ 3 │ 'user_3' │ '[{"id":5,"title":"user_3_title_1"},{"id":6,"title":"user_3_title_2"}]' │ 76 | │ 2 │ 4 │ 'user_4' │ '[{"id":7,"title":"user_4_title_1"},{"id":8,"title":"user_4_title_2"}]' │ 77 | └─────────┴────┴──────────┴─────────────────────────────────────────────────────────────────────────┘ 78 | ``` 79 | 80 | ```none 81 | // 查询第 4-8 条数据 82 | userRepo.createQueryBuilder('user').leftJoinAndSelect('user.posts', 'post').skip(3).take(5).getMany(); 83 | 84 | // 生成的 SQL 85 | query: SELECT DISTINCT "distinctAlias"."user_id" as "ids_user_id" FROM (SELECT "user"."id" AS "user_id", "user"."name" AS "user_name", "post"."id" AS "post_id", "post"."title" AS "post_title", "post"."userId" AS "post_userId" FROM "user" "user" LEFT JOIN "post" "post" ON "post"."userId"="user"."id") "distinctAlias" ORDER BY "user_id" ASC LIMIT 5 OFFSET 3 86 | query: SELECT "user"."id" AS "user_id", "user"."name" AS "user_name", "post"."id" AS "post_id", "post"."title" AS "post_title", "post"."userId" AS "post_userId" FROM "user" "user" LEFT JOIN "post" "post" ON "post"."userId"="user"."id" WHERE "user"."id" IN (4, 5, 6, 7, 8) 87 | 88 | // 结果集 89 | ┌─────────┬────┬──────────┬───────────────────────────────────────────────────────────────────────────┐ 90 | │ (index) │ id │ name │ posts │ 91 | ├─────────┼────┼──────────┼───────────────────────────────────────────────────────────────────────────┤ 92 | │ 0 │ 4 │ 'user_4' │ '[{"id":7,"title":"user_4_title_1"},{"id":8,"title":"user_4_title_2"}]' │ 93 | │ 1 │ 5 │ 'user_5' │ '[{"id":9,"title":"user_5_title_1"},{"id":10,"title":"user_5_title_2"}]' │ 94 | │ 2 │ 6 │ 'user_6' │ '[{"id":11,"title":"user_6_title_1"},{"id":12,"title":"user_6_title_2"}]' │ 95 | │ 3 │ 7 │ 'user_7' │ '[{"id":13,"title":"user_7_title_1"},{"id":14,"title":"user_7_title_2"}]' │ 96 | │ 4 │ 8 │ 'user_8' │ '[{"id":15,"title":"user_8_title_1"},{"id":16,"title":"user_8_title_2"}]' │ 97 | └─────────┴────┴──────────┴───────────────────────────────────────────────────────────────────────────┘ 98 | ``` 99 | 100 | #### 测试数据 101 | 102 | 用户表 103 | 104 | ![user](./images/user.png) 105 | 106 | 用户帖子表 107 | 108 | ![post](./images/post.png) -------------------------------------------------------------------------------- /issues/typeorm/pagination/images/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzzzzzy/Nestjs-Learning/70800dacebf241f2708561776e4b86fb8e14ffed/issues/typeorm/pagination/images/post.png -------------------------------------------------------------------------------- /issues/typeorm/pagination/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzzzzzy/Nestjs-Learning/70800dacebf241f2708561776e4b86fb8e14ffed/issues/typeorm/pagination/images/user.png -------------------------------------------------------------------------------- /issues/typeorm/pagination/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typeorm-pagination-test", 3 | "version": "1.0.0", 4 | "description": "test typeorm pagination.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/ts-node src/pagination.test.ts" 8 | }, 9 | "author": "dzzzzzy", 10 | "license": "MIT", 11 | "dependencies": { 12 | "pg": "^7.4.3", 13 | "typeorm": "^0.2.18", 14 | "typescript": "^3.0.1" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^10.5.7", 18 | "ts-node": "^8.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /issues/typeorm/pagination/src/pagination.test.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from 'typeorm'; 2 | 3 | import { Post } from './post.entity'; 4 | import { User } from './user.entity'; 5 | 6 | createConnection({ 7 | type: 'postgres', 8 | host: 'localhost', 9 | port: 5432, 10 | username: 'postgres', 11 | password: '123456', 12 | database: 'postgres', 13 | entities: [__dirname + '/*.entity.ts'], 14 | maxQueryExecutionTime: 1000, 15 | synchronize: true, 16 | dropSchema: true, 17 | // logging: true, 18 | // logger: 'advanced-console' 19 | }).then(async connection => { 20 | const userRepo = connection.getRepository(User); 21 | const postRepo = connection.getRepository(Post); 22 | 23 | for (let userIndex = 0; userIndex < 10; userIndex++) { 24 | await userRepo.save(userRepo.create({ 25 | name: `user_${userIndex + 1}`, 26 | posts: 27 | [ 28 | postRepo.create({ title: `user_${userIndex + 1}_title_1` }), 29 | postRepo.create({ title: `user_${userIndex + 1}_title_2` }) 30 | ] 31 | })); 32 | } 33 | // getManyAndCount 返回一个长度为2的元组,[0] 是分页后的数据数组, [1] 是所有数据总数 34 | const userInfoWithOL = await userRepo.createQueryBuilder('user').leftJoinAndSelect('user.posts', 'post').offset(3).limit(5).getManyAndCount(); 35 | 36 | const userInfoWithST = await userRepo.createQueryBuilder('user').leftJoinAndSelect('user.posts', 'post').skip(3).take(5).getManyAndCount(); 37 | 38 | const userInfoTableWithOL = userInfoWithOL[0].map(item => { 39 | return { 40 | id: item.id, 41 | name: item.name, 42 | posts: JSON.stringify(item.posts.sort((current, next) => current.id - next.id)) 43 | }; 44 | }); 45 | 46 | const userInfoTableWithST = userInfoWithST[0].map(item => { 47 | return { 48 | id: item.id, 49 | name: item.name, 50 | posts: JSON.stringify(item.posts.sort((current, next) => current.id - next.id)) 51 | }; 52 | }); 53 | 54 | console.table(userInfoTableWithOL); 55 | 56 | console.table(userInfoTableWithST); 57 | 58 | connection.close(); 59 | }); -------------------------------------------------------------------------------- /issues/typeorm/pagination/src/post.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | import { User } from './user.entity'; 4 | 5 | @Entity('post') 6 | export class Post { 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | 10 | @Column() 11 | title: string; 12 | 13 | @ManyToOne(type => User, user => user.posts) 14 | user: User; 15 | } -------------------------------------------------------------------------------- /issues/typeorm/pagination/src/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | import { Post } from './post.entity'; 4 | 5 | @Entity('user') 6 | export class User { 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | 10 | @Column({ 11 | unique: true 12 | }) 13 | name: string; 14 | 15 | @OneToMany(type => Post, post => post.user, { 16 | cascade: ['insert'] 17 | }) 18 | posts: Post[]; 19 | } -------------------------------------------------------------------------------- /issues/typeorm/pagination/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "baseUrl": "./", 5 | "declaration": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "noImplicitAny": false, 10 | "noLib": false, 11 | "lib": [ 12 | "es6", 13 | "dom", 14 | "esnext.asynciterable" 15 | ], 16 | "noUnusedLocals": false, 17 | "removeComments": true, 18 | "strict": false, 19 | "strictPropertyInitialization": false, 20 | "target": "es6", 21 | "outDir": "./test-out" 22 | }, 23 | "include": [ 24 | "./**/*.ts" 25 | ] 26 | } -------------------------------------------------------------------------------- /issues/typeorm/pagination/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^10.5.7": 6 | version "10.14.13" 7 | resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-10.14.13.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-10.14.13.tgz#ac786d623860adf39a3f51d629480aacd6a6eec7" 8 | integrity sha1-rHhtYjhgrfOaP1HWKUgKrNam7sc= 9 | 10 | ansi-regex@^2.0.0: 11 | version "2.1.1" 12 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 13 | 14 | ansi-regex@^4.1.0: 15 | version "4.1.0" 16 | resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 17 | integrity sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc= 18 | 19 | ansi-styles@^2.2.1: 20 | version "2.2.1" 21 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 22 | 23 | ansi-styles@^3.2.0, ansi-styles@^3.2.1: 24 | version "3.2.1" 25 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 26 | dependencies: 27 | color-convert "^1.9.0" 28 | 29 | any-promise@^1.0.0: 30 | version "1.3.0" 31 | resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" 32 | 33 | app-root-path@^2.0.1: 34 | version "2.1.0" 35 | resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.1.0.tgz#98bf6599327ecea199309866e8140368fd2e646a" 36 | 37 | arg@^4.1.0: 38 | version "4.1.1" 39 | resolved "https://registry.npm.taobao.org/arg/download/arg-4.1.1.tgz#485f8e7c390ce4c5f78257dbea80d4be11feda4c" 40 | integrity sha1-SF+OfDkM5MX3glfb6oDUvhH+2kw= 41 | 42 | argparse@^1.0.7: 43 | version "1.0.10" 44 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 45 | dependencies: 46 | sprintf-js "~1.0.2" 47 | 48 | balanced-match@^1.0.0: 49 | version "1.0.0" 50 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 51 | 52 | base64-js@^1.0.2: 53 | version "1.3.0" 54 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" 55 | 56 | brace-expansion@^1.1.7: 57 | version "1.1.11" 58 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 59 | dependencies: 60 | balanced-match "^1.0.0" 61 | concat-map "0.0.1" 62 | 63 | buffer-from@^1.0.0: 64 | version "1.1.1" 65 | resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 66 | integrity sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8= 67 | 68 | buffer-writer@2.0.0: 69 | version "2.0.0" 70 | resolved "https://registry.npm.taobao.org/buffer-writer/download/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" 71 | integrity sha1-zn64Gjj3gp2wnIc/L7t5LAyY7AQ= 72 | 73 | buffer@^5.1.0: 74 | version "5.2.0" 75 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.0.tgz#53cf98241100099e9eeae20ee6d51d21b16e541e" 76 | dependencies: 77 | base64-js "^1.0.2" 78 | ieee754 "^1.1.4" 79 | 80 | camelcase@^5.0.0: 81 | version "5.3.1" 82 | resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 83 | integrity sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA= 84 | 85 | chalk@^1.1.1: 86 | version "1.1.3" 87 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 88 | dependencies: 89 | ansi-styles "^2.2.1" 90 | escape-string-regexp "^1.0.2" 91 | has-ansi "^2.0.0" 92 | strip-ansi "^3.0.0" 93 | supports-color "^2.0.0" 94 | 95 | chalk@^2.3.0: 96 | version "2.4.1" 97 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" 98 | dependencies: 99 | ansi-styles "^3.2.1" 100 | escape-string-regexp "^1.0.5" 101 | supports-color "^5.3.0" 102 | 103 | chalk@^2.4.2: 104 | version "2.4.2" 105 | resolved "https://registry.npm.taobao.org/chalk/download/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 106 | integrity sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ= 107 | dependencies: 108 | ansi-styles "^3.2.1" 109 | escape-string-regexp "^1.0.5" 110 | supports-color "^5.3.0" 111 | 112 | cli-highlight@^2.0.0: 113 | version "2.1.1" 114 | resolved "https://registry.npm.taobao.org/cli-highlight/download/cli-highlight-2.1.1.tgz#2180223d51618b112f4509cf96e4a6c750b07e97" 115 | integrity sha1-IYAiPVFhixEvRQnPluSmx1Cwfpc= 116 | dependencies: 117 | chalk "^2.3.0" 118 | highlight.js "^9.6.0" 119 | mz "^2.4.0" 120 | parse5 "^4.0.0" 121 | yargs "^13.0.0" 122 | 123 | cliui@^5.0.0: 124 | version "5.0.0" 125 | resolved "https://registry.npm.taobao.org/cliui/download/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" 126 | integrity sha1-3u/P2y6AB4SqNPRvoI4GhRx7u8U= 127 | dependencies: 128 | string-width "^3.1.0" 129 | strip-ansi "^5.2.0" 130 | wrap-ansi "^5.1.0" 131 | 132 | color-convert@^1.9.0: 133 | version "1.9.2" 134 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" 135 | dependencies: 136 | color-name "1.1.1" 137 | 138 | color-name@1.1.1: 139 | version "1.1.1" 140 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" 141 | 142 | concat-map@0.0.1: 143 | version "0.0.1" 144 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 145 | 146 | debug@^4.1.1: 147 | version "4.1.1" 148 | resolved "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 149 | integrity sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E= 150 | dependencies: 151 | ms "^2.1.1" 152 | 153 | decamelize@^1.2.0: 154 | version "1.2.0" 155 | resolved "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 156 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 157 | 158 | diff@^4.0.1: 159 | version "4.0.1" 160 | resolved "https://registry.npm.taobao.org/diff/download/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" 161 | integrity sha1-DGZ8tGfru1zqfxTxNcwtuneAqP8= 162 | 163 | dotenv@^6.2.0: 164 | version "6.2.0" 165 | resolved "https://registry.npm.taobao.org/dotenv/download/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" 166 | integrity sha1-lBwEEFNdlCyL7PKNPzV9vZ1HYGQ= 167 | 168 | emoji-regex@^7.0.1: 169 | version "7.0.3" 170 | resolved "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 171 | integrity sha1-kzoEBShgyF6DwSJHnEdIqOTHIVY= 172 | 173 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 174 | version "1.0.5" 175 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 176 | 177 | esprima@^4.0.0: 178 | version "4.0.1" 179 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 180 | 181 | figlet@^1.1.1: 182 | version "1.2.0" 183 | resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410" 184 | 185 | find-up@^3.0.0: 186 | version "3.0.0" 187 | resolved "https://registry.npm.taobao.org/find-up/download/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 188 | integrity sha1-SRafHXmTQwZG2mHsxa41XCHJe3M= 189 | dependencies: 190 | locate-path "^3.0.0" 191 | 192 | fs.realpath@^1.0.0: 193 | version "1.0.0" 194 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 195 | 196 | get-caller-file@^2.0.1: 197 | version "2.0.5" 198 | resolved "https://registry.npm.taobao.org/get-caller-file/download/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 199 | integrity sha1-T5RBKoLbMvNuOwuXQfipf+sDH34= 200 | 201 | glob@^7.1.2: 202 | version "7.1.2" 203 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 204 | dependencies: 205 | fs.realpath "^1.0.0" 206 | inflight "^1.0.4" 207 | inherits "2" 208 | minimatch "^3.0.4" 209 | once "^1.3.0" 210 | path-is-absolute "^1.0.0" 211 | 212 | has-ansi@^2.0.0: 213 | version "2.0.0" 214 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 215 | dependencies: 216 | ansi-regex "^2.0.0" 217 | 218 | has-flag@^3.0.0: 219 | version "3.0.0" 220 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 221 | 222 | highlight.js@^9.6.0: 223 | version "9.12.0" 224 | resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" 225 | 226 | ieee754@^1.1.4: 227 | version "1.1.12" 228 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" 229 | 230 | inflight@^1.0.4: 231 | version "1.0.6" 232 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 233 | dependencies: 234 | once "^1.3.0" 235 | wrappy "1" 236 | 237 | inherits@2: 238 | version "2.0.3" 239 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 240 | 241 | is-fullwidth-code-point@^2.0.0: 242 | version "2.0.0" 243 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 244 | 245 | js-yaml@^3.13.1: 246 | version "3.13.1" 247 | resolved "https://registry.npm.taobao.org/js-yaml/download/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 248 | integrity sha1-r/FRswv9+o5J4F2iLnQV6d+jeEc= 249 | dependencies: 250 | argparse "^1.0.7" 251 | esprima "^4.0.0" 252 | 253 | locate-path@^3.0.0: 254 | version "3.0.0" 255 | resolved "https://registry.npm.taobao.org/locate-path/download/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 256 | integrity sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4= 257 | dependencies: 258 | p-locate "^3.0.0" 259 | path-exists "^3.0.0" 260 | 261 | make-error@^1.1.1: 262 | version "1.3.5" 263 | resolved "https://registry.npm.taobao.org/make-error/download/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" 264 | integrity sha1-7+ToH22yjK3WBccPKcgxtY73dsg= 265 | 266 | minimatch@^3.0.4: 267 | version "3.0.4" 268 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 269 | dependencies: 270 | brace-expansion "^1.1.7" 271 | 272 | minimist@0.0.8: 273 | version "0.0.8" 274 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 275 | 276 | mkdirp@^0.5.1: 277 | version "0.5.1" 278 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 279 | dependencies: 280 | minimist "0.0.8" 281 | 282 | ms@^2.1.1: 283 | version "2.1.2" 284 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 285 | integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= 286 | 287 | mz@^2.4.0: 288 | version "2.7.0" 289 | resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" 290 | dependencies: 291 | any-promise "^1.0.0" 292 | object-assign "^4.0.1" 293 | thenify-all "^1.0.0" 294 | 295 | object-assign@^4.0.1: 296 | version "4.1.1" 297 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 298 | 299 | once@^1.3.0: 300 | version "1.4.0" 301 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 302 | dependencies: 303 | wrappy "1" 304 | 305 | p-limit@^2.0.0: 306 | version "2.2.0" 307 | resolved "https://registry.npm.taobao.org/p-limit/download/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" 308 | integrity sha1-QXyZQeYCepq8ulCS3SkE4lW1+8I= 309 | dependencies: 310 | p-try "^2.0.0" 311 | 312 | p-locate@^3.0.0: 313 | version "3.0.0" 314 | resolved "https://registry.npm.taobao.org/p-locate/download/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 315 | integrity sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ= 316 | dependencies: 317 | p-limit "^2.0.0" 318 | 319 | p-try@^2.0.0: 320 | version "2.2.0" 321 | resolved "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 322 | integrity sha1-yyhoVA4xPWHeWPr741zpAE1VQOY= 323 | 324 | packet-reader@1.0.0: 325 | version "1.0.0" 326 | resolved "https://registry.npm.taobao.org/packet-reader/download/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" 327 | integrity sha1-kjjlSA3tq6z+H+PydxBj8WQVfXQ= 328 | 329 | parent-require@^1.0.0: 330 | version "1.0.0" 331 | resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" 332 | 333 | parse5@^4.0.0: 334 | version "4.0.0" 335 | resolved "https://registry.npm.taobao.org/parse5/download/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" 336 | integrity sha1-bXhlbj2o14tOwLkG98CO8d/j9gg= 337 | 338 | path-exists@^3.0.0: 339 | version "3.0.0" 340 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 341 | 342 | path-is-absolute@^1.0.0: 343 | version "1.0.1" 344 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 345 | 346 | pg-connection-string@0.1.3: 347 | version "0.1.3" 348 | resolved "https://registry.npm.taobao.org/pg-connection-string/download/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" 349 | integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc= 350 | 351 | pg-int8@1.0.1: 352 | version "1.0.1" 353 | resolved "https://registry.npm.taobao.org/pg-int8/download/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" 354 | integrity sha1-lDvUY79bcbQXARX4D478mgwOt4w= 355 | 356 | pg-pool@^2.0.4: 357 | version "2.0.6" 358 | resolved "https://registry.npm.taobao.org/pg-pool/download/pg-pool-2.0.6.tgz#7b561a482feb0a0e599b58b5137fd2db3ad8111c" 359 | integrity sha1-e1YaSC/rCg5Zm1i1E3/S2zrYERw= 360 | 361 | pg-types@~2.0.0: 362 | version "2.0.1" 363 | resolved "https://registry.npm.taobao.org/pg-types/download/pg-types-2.0.1.tgz#b8585a37f2a9c7b386747e44574799549e5f4933" 364 | integrity sha1-uFhaN/Kpx7OGdH5EV0eZVJ5fSTM= 365 | dependencies: 366 | pg-int8 "1.0.1" 367 | postgres-array "~2.0.0" 368 | postgres-bytea "~1.0.0" 369 | postgres-date "~1.0.4" 370 | postgres-interval "^1.1.0" 371 | 372 | pg@^7.4.3: 373 | version "7.11.0" 374 | resolved "https://registry.npm.taobao.org/pg/download/pg-7.11.0.tgz#a8b9ae9cf19199b7952b72957573d0a9c5e67c0c" 375 | integrity sha1-qLmunPGRmbeVK3KVdXPQqcXmfAw= 376 | dependencies: 377 | buffer-writer "2.0.0" 378 | packet-reader "1.0.0" 379 | pg-connection-string "0.1.3" 380 | pg-pool "^2.0.4" 381 | pg-types "~2.0.0" 382 | pgpass "1.x" 383 | semver "4.3.2" 384 | 385 | pgpass@1.x: 386 | version "1.0.2" 387 | resolved "https://registry.npm.taobao.org/pgpass/download/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" 388 | integrity sha1-Knu0G2BltnkH6R2hsHwYR8h3swY= 389 | dependencies: 390 | split "^1.0.0" 391 | 392 | postgres-array@~2.0.0: 393 | version "2.0.0" 394 | resolved "https://registry.npm.taobao.org/postgres-array/download/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" 395 | integrity sha1-SPj84FT7xpZxmZMpuINLdyZS2C4= 396 | 397 | postgres-bytea@~1.0.0: 398 | version "1.0.0" 399 | resolved "https://registry.npm.taobao.org/postgres-bytea/download/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" 400 | integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= 401 | 402 | postgres-date@~1.0.4: 403 | version "1.0.4" 404 | resolved "https://registry.npm.taobao.org/postgres-date/download/postgres-date-1.0.4.tgz#1c2728d62ef1bff49abdd35c1f86d4bdf118a728" 405 | integrity sha1-HCco1i7xv/SavdNcH4bUvfEYpyg= 406 | 407 | postgres-interval@^1.1.0: 408 | version "1.2.0" 409 | resolved "https://registry.npm.taobao.org/postgres-interval/download/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" 410 | integrity sha1-tGDILLFYdQd4iBmgaqD//bNURpU= 411 | dependencies: 412 | xtend "^4.0.0" 413 | 414 | reflect-metadata@^0.1.13: 415 | version "0.1.13" 416 | resolved "https://registry.npm.taobao.org/reflect-metadata/download/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" 417 | integrity sha1-Z648pXyXKiqhZCsQ/jY/4y1J3Ag= 418 | 419 | require-directory@^2.1.1: 420 | version "2.1.1" 421 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 422 | 423 | require-main-filename@^2.0.0: 424 | version "2.0.0" 425 | resolved "https://registry.npm.taobao.org/require-main-filename/download/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 426 | integrity sha1-0LMp7MfMD2Fkn2IhW+aa9UqomJs= 427 | 428 | sax@>=0.6.0: 429 | version "1.2.4" 430 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 431 | 432 | semver@4.3.2: 433 | version "4.3.2" 434 | resolved "https://registry.npm.taobao.org/semver/download/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" 435 | integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= 436 | 437 | set-blocking@^2.0.0: 438 | version "2.0.0" 439 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 440 | 441 | source-map-support@^0.5.6: 442 | version "0.5.12" 443 | resolved "https://registry.npm.taobao.org/source-map-support/download/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" 444 | integrity sha1-tPOxDVGFelrwE4086AA7IBYT1Zk= 445 | dependencies: 446 | buffer-from "^1.0.0" 447 | source-map "^0.6.0" 448 | 449 | source-map@^0.6.0: 450 | version "0.6.1" 451 | resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 452 | integrity sha1-dHIq8y6WFOnCh6jQu95IteLxomM= 453 | 454 | split@^1.0.0: 455 | version "1.0.1" 456 | resolved "https://registry.npm.taobao.org/split/download/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" 457 | integrity sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k= 458 | dependencies: 459 | through "2" 460 | 461 | sprintf-js@~1.0.2: 462 | version "1.0.3" 463 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 464 | 465 | string-width@^3.0.0, string-width@^3.1.0: 466 | version "3.1.0" 467 | resolved "https://registry.npm.taobao.org/string-width/download/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 468 | integrity sha1-InZ74htirxCBV0MG9prFG2IgOWE= 469 | dependencies: 470 | emoji-regex "^7.0.1" 471 | is-fullwidth-code-point "^2.0.0" 472 | strip-ansi "^5.1.0" 473 | 474 | strip-ansi@^3.0.0: 475 | version "3.0.1" 476 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 477 | dependencies: 478 | ansi-regex "^2.0.0" 479 | 480 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: 481 | version "5.2.0" 482 | resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 483 | integrity sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4= 484 | dependencies: 485 | ansi-regex "^4.1.0" 486 | 487 | supports-color@^2.0.0: 488 | version "2.0.0" 489 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 490 | 491 | supports-color@^5.3.0: 492 | version "5.4.0" 493 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" 494 | dependencies: 495 | has-flag "^3.0.0" 496 | 497 | thenify-all@^1.0.0: 498 | version "1.6.0" 499 | resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" 500 | dependencies: 501 | thenify ">= 3.1.0 < 4" 502 | 503 | "thenify@>= 3.1.0 < 4": 504 | version "3.3.0" 505 | resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" 506 | dependencies: 507 | any-promise "^1.0.0" 508 | 509 | through@2: 510 | version "2.3.8" 511 | resolved "https://registry.npm.taobao.org/through/download/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 512 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 513 | 514 | ts-node@^8.0.0: 515 | version "8.3.0" 516 | resolved "https://registry.npm.taobao.org/ts-node/download/ts-node-8.3.0.tgz#e4059618411371924a1fb5f3b125915f324efb57" 517 | integrity sha1-5AWWGEETcZJKH7XzsSWRXzJO+1c= 518 | dependencies: 519 | arg "^4.1.0" 520 | diff "^4.0.1" 521 | make-error "^1.1.1" 522 | source-map-support "^0.5.6" 523 | yn "^3.0.0" 524 | 525 | tslib@^1.9.0: 526 | version "1.10.0" 527 | resolved "https://registry.npm.taobao.org/tslib/download/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" 528 | integrity sha1-w8GflZc/sKYpc/sJ2Q2WHuQ+XIo= 529 | 530 | typeorm@^0.2.18: 531 | version "0.2.18" 532 | resolved "https://registry.npm.taobao.org/typeorm/download/typeorm-0.2.18.tgz#8ae1d21104117724af41ddc11035c40a705e1de8" 533 | integrity sha1-iuHSEQQRdySvQd3BEDXECnBeHeg= 534 | dependencies: 535 | app-root-path "^2.0.1" 536 | buffer "^5.1.0" 537 | chalk "^2.4.2" 538 | cli-highlight "^2.0.0" 539 | debug "^4.1.1" 540 | dotenv "^6.2.0" 541 | glob "^7.1.2" 542 | js-yaml "^3.13.1" 543 | mkdirp "^0.5.1" 544 | reflect-metadata "^0.1.13" 545 | tslib "^1.9.0" 546 | xml2js "^0.4.17" 547 | yargonaut "^1.1.2" 548 | yargs "^13.2.1" 549 | 550 | typescript@^3.0.1: 551 | version "3.5.3" 552 | resolved "https://registry.npm.taobao.org/typescript/download/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" 553 | integrity sha1-yDD2V/k/HqhGgZ6SkJL1/lmD6Xc= 554 | 555 | which-module@^2.0.0: 556 | version "2.0.0" 557 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 558 | 559 | wrap-ansi@^5.1.0: 560 | version "5.1.0" 561 | resolved "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" 562 | integrity sha1-H9H2cjXVttD+54EFYAG/tpTAOwk= 563 | dependencies: 564 | ansi-styles "^3.2.0" 565 | string-width "^3.0.0" 566 | strip-ansi "^5.0.0" 567 | 568 | wrappy@1: 569 | version "1.0.2" 570 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 571 | 572 | xml2js@^0.4.17: 573 | version "0.4.19" 574 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" 575 | dependencies: 576 | sax ">=0.6.0" 577 | xmlbuilder "~9.0.1" 578 | 579 | xmlbuilder@~9.0.1: 580 | version "9.0.7" 581 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" 582 | 583 | xtend@^4.0.0: 584 | version "4.0.2" 585 | resolved "https://registry.npm.taobao.org/xtend/download/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 586 | integrity sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q= 587 | 588 | y18n@^4.0.0: 589 | version "4.0.0" 590 | resolved "https://registry.npm.taobao.org/y18n/download/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" 591 | integrity sha1-le+U+F7MgdAHwmThkKEg8KPIVms= 592 | 593 | yargonaut@^1.1.2: 594 | version "1.1.3" 595 | resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.3.tgz#1cf6bbe511ecc865d6014203385628bcc39b0493" 596 | dependencies: 597 | chalk "^1.1.1" 598 | figlet "^1.1.1" 599 | parent-require "^1.0.0" 600 | 601 | yargs-parser@^13.1.1: 602 | version "13.1.1" 603 | resolved "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" 604 | integrity sha1-0mBYUyqgbTZf4JH2ofwGsvfl7KA= 605 | dependencies: 606 | camelcase "^5.0.0" 607 | decamelize "^1.2.0" 608 | 609 | yargs@^13.0.0, yargs@^13.2.1: 610 | version "13.3.0" 611 | resolved "https://registry.npm.taobao.org/yargs/download/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" 612 | integrity sha1-TGV6VeB+Xyz5R/ijZlZ8BKDe3IM= 613 | dependencies: 614 | cliui "^5.0.0" 615 | find-up "^3.0.0" 616 | get-caller-file "^2.0.1" 617 | require-directory "^2.1.1" 618 | require-main-filename "^2.0.0" 619 | set-blocking "^2.0.0" 620 | string-width "^3.0.0" 621 | which-module "^2.0.0" 622 | y18n "^4.0.0" 623 | yargs-parser "^13.1.1" 624 | 625 | yn@^3.0.0: 626 | version "3.1.0" 627 | resolved "https://registry.npm.taobao.org/yn/download/yn-3.1.0.tgz#fcbe2db63610361afcc5eb9e0ac91e976d046114" 628 | integrity sha1-/L4ttjYQNhr8xeueCskel20EYRQ= 629 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges" 5 | ] 6 | } 7 | --------------------------------------------------------------------------------