经常初始化项目, 有些功能都是通用的, 每次都进行通用化功能的实现比较繁琐, 因此提取到该项目中. github nestjs-pg-tmplate
相关功能
- ts strict true
- debugger vscode
- eslint, prettier, cSpell, commitLint, husky
- config yml env 支持
- validate and transform
- docker compose pg redis
- typeorm migration soft-delete
- user login register rbac
- app metaData
- open sdk generate
- logger dayjs
ts strict true
{
// ...
"strict": true,
"strictNullChecks": true,
"noImplicitAny": false,
"strictBindCallApply": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true
// ...
}
debugger vscode
// .vscode/settings.json
{
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.indentSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach NestJS WS",
"port": 9229,
"restart": true
}
]
}
// .vscode/extensions.json
{
"recommendations": [
"dbaeumer.vscode-eslint", // ESLint 插件
"esbenp.prettier-vscode", // Prettier 插件
// "rangav.vscode-thunder-client", // thunder client
"streetsidesoftware.code-spell-checker" // spell
]
}
eslint, prettier, cSpell, commitLint, husky
prettier 相关更改
endOfLine: 'auto' // 主要针对不同系统换行及结尾问题
eslint, prettier 默认 nestjs 项目初始化会自动安装, 仅需针对配置文件进行相关更新即可
# npx husky init
"prepare": "husky"
pnpm i @commitlint/cli @commitlint/config-conventional husky -D
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
}
# .husky/pre-commit
pnpm test
pnpm lint
# .husky/commit-msg
npx --no -- commitlint --edit "$1"
config yml env
这里未将项目配置单独抽成相关包, 同时针对配置类型进行了严格校验
pnpm add --save class-validator class-transformer @nestjs/config js-yaml
pnpm add @types/js-yaml -D
// src/config/config.ts
import {
IsEnum,
IsNumber,
IsOptional,
IsString,
Validate,
ValidateIf,
} from 'class-validator'
export enum Env {
dev = 'dev',
pro = 'pro',
}
export class db {
@IsString()
host!: string
@IsNumber()
port!: number
@IsString()
username!: string
@IsString()
password!: string
@IsString()
database!: string
}
export class Security {
@IsString()
jwtKey!: string
/** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). Eg: 60, "2 days", "10h", "7d" */
@IsOptional()
@ValidateIf((_, val) => typeof val === 'number')
@IsNumber()
@ValidateIf((_, val) => typeof val === 'string')
@IsString()
jwtExpiresIn?: number = 30 * 60
}
export class Config {
@IsOptional()
@IsEnum(Env)
env: Env = Env.dev
@IsOptional()
@IsString()
appName: string = 'server'
@Validate(() => Security)
security!: Security
@Validate(() => db)
db!: db
}
// src/config/configuration.ts
import { readFileSync } from 'fs'
import * as yaml from 'js-yaml'
import { join } from 'path'
import { Config } from './config'
import { plainToInstance } from 'class-transformer'
import { validateSync } from 'class-validator'
const YAML_CONFIG_FILENAME = 'config.yml'
let config: Config | undefined
export default () => {
if (config) return config
const configData = yaml.load(
readFileSync(join(process.cwd(), YAML_CONFIG_FILENAME), 'utf8')
) as Record<string, any>
// transform and valiate
const target = plainToInstance(Config, configData)
const error = validateSync(target)
if (error.length > 0) {
throw new Error(error.map(err => err.toString(true)).join('\n'))
} else {
config = target
return target
}
}
validate and transform
极其重要, 主要是验证和转换规则
// src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { ValidationPipe } from '@nestjs/common'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalPipes(
new ValidationPipe({
transform: true,
transformOptions: {
strategy: 'excludeAll',
enableImplicitConversion: true,
excludeExtraneousValues: true,
},
})
)
app.enableCors()
await app.listen(3000)
}
bootstrap()
docker compose pg redis
环境依赖安装, 主要针对本地开发环境, 生产环境则自行考虑, 这里增加 redis 是因为 redis 常用于发布订阅或缓存, 但这些功能模板并未实现, 后续考虑
docker compose pg redis 相关提交记录
# .gitignore
volumes
# docker-compose-depend.yml
services:
redis:
image: redis:latest
container_name: redis
restart: unless-stopped
privileged: true
volumes:
- ./volumes/redis/datadir:/data
- ./volumes/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf
- ./volumes/redis/logs:/logs
command:
# redis-server
# 两个写入操作 只是为了解决启动后警告 可以去掉
/bin/bash -c "echo 511 > /proc/sys/net/core/somaxconn && echo never > /sys/kernel/mm/transparent_hugepage/enabled && redis-server /usr/local/etc/redis/redis.conf"
ports:
- 6379:6379
networks:
- dev
pgsql:
image: postgres:latest
restart: unless-stopped
container_name: pgsql-new
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- TZ=Asia/Shanghai
ports:
- '5434:5432'
volumes:
# 初始的数据文件
- ./volumes/pgsql/data:/var/lib/postgresql/data
networks:
- dev
networks:
dev:
name: local-dev
# redis pgsql
docker compose -f docker-compose-depend.yml up -d
typeorm pg
这里主要抽 db 模块出来作为 lib, 支持 typeorm 及 基础实体, 软删除, 迁移, 回滚, 实体同步等相关功能
nest g lib db
pnpm add --save @nestjs/typeorm typeorm pg
user login register rbac
需要进一步考虑, 这里的权限其实就是接口的元数据信息, 角色组合权限, 用户的权限则是角色权限和用户权限的合集
user login register rbac 相关提交记录
pnpm add bcrypt @nestjs/jwt @nestjs/passport passport-jwt
nest g mo auth
nest g co auth
nest g s auth
nest g mo user
nest g co user
nest g s user
nest g mo role
nest g co role
nest g s role
代码较多, 仅展示应用元数据相关实现, 其他移步 代码库相关提交 查看
import { Controller, Get } from '@nestjs/common'
import { DiscoveryService, Reflector } from '@nestjs/core'
import { Describe, DESCRIBE_KEY } from 'src/_decorator/describe'
import { ControllerInfo } from './dto/meta.dto'
// 做进程内缓存, 避免重复计算
let allControllerInfo: ControllerInfo[] | undefined
/**
* 权限管理
*/
@Controller('meta')
@Describe('权限管理')
export class MetaController {
constructor(
private readonly discoveryService: DiscoveryService,
private readonly reflector: Reflector
) {}
/**
* 获取所有权限信息
* @returns 权限信息
*/
@Get()
@Describe('所有权限信息')
allMetaData(): ControllerInfo[] {
if (allControllerInfo) return allControllerInfo
const controllers = this.discoveryService.getControllers()
const result: ControllerInfo[] = []
controllers.forEach(controller => {
const name = controller.name
const describe = this.reflector.get(DESCRIBE_KEY, controller.metatype)
if (!describe) return
const methods = Object.getOwnPropertyNames(controller.metatype.prototype)
const resMethods = methods
.map(method => {
if (method !== 'constructor') {
const methodDescribe = Reflect.getMetadata(
DESCRIBE_KEY,
controller.metatype.prototype[method]
)
if (methodDescribe)
return {
name: method,
describe: methodDescribe,
context: `${name}/${method}`,
}
}
})
.filter(Boolean) as ControllerInfo['methods']
result.push({ name, describe, methods: resMethods, context: name })
})
allControllerInfo = result
return result
}
}
open sdk generate
这里使用 openapi 分支进行文档书写及生成 sdk, 目前不发 npm 包而是使用子仓库的方式引用
pnpm add @nestjs/swagger @hey-api/openapi-ts @hey-api/client-fetch -D
# change branch to openapi
npm version patch
git push --tags
git push
dayjs and logger
日志相关
pnpm i winston nest-winston dayjs
import * as winston from 'winston'
import {
utilities as nestWinstonModuleUtilities,
WinstonModule,
} from 'nest-winston'
import * as path from 'path'
import configuration from 'src/config/configuration'
import { Env } from 'src/config/config'
const transports = {
console: new winston.transports.Console({
level: 'silly',
}),
logFile: new winston.transports.File({
dirname: path.join(process.cwd(), 'logs'),
filename: 'log.json',
level: 'silly',
}),
}
const getLogger = async () => {
const config = configuration()
// 如果是本地测试环境则启用 nest 风格的 log format
if (config.env == Env.dev) {
transports.console = new winston.transports.Console({
level: 'silly',
format: winston.format.combine(
winston.format.timestamp({
format: () =>
new Date().toLocaleString('chinese', {
timeZone: 'Asia/Shanghai',
hour12: false,
}),
}),
nestWinstonModuleUtilities.format.nestLike(config.appName)
),
})
}
const logger = WinstonModule.createLogger({
transports: [transports.console, transports.logFile],
level: 'silly',
})
return logger
}
export default getLogger
数据迁移
# 生成迁移 (根据实体信息)
npm run typeorm migration:generate -n ./libs/db/src/migrations/fix-data
# 新建迁移
npx typeorm migration:create ./libs/db/src/migrations/init-data
# 迁移
npm run typeorm migration:run --fake
# 回滚
npm run typeorm migration:revert
# 同步实体
npm run typeorm schema:sync
提交代码
pnpm lint
# git commit -m "type: subject"
# rules view https://www.npmjs.com/package/@commitlint/config-conventional
这篇文章详细阐述了从初始化NestJS项目到最终代码提交的完整流程,涵盖了依赖管理、环境配置、目录结构、测试规范以及版本控制等多个关键环节。特别值得注意的是,文中强调了使用Nx工具链来提升开发效率,并通过严格的代码风格和格式化规则确保团队协作的一致性。此外,对数据迁移和文档生成的处理也体现了项目管理的专业性。总的来说,这篇文章为开发者提供了一份全面且实用的NestJS项目开发指南。
博客内容主要介绍了一个基于NestJS和PostgreSQL的模板项目,以及其中的一些功能和实现。博客首先介绍了使用Docker Compose来配置和启动PostgreSQL和Redis的容器,并提供了相关的配置文件和命令。接下来,博客介绍了使用TypeORM来管理PostgreSQL数据库,并提供了相关的代码示例。然后,博客讨论了用户登录、注册和权限管理的实现,包括使用bcrypt进行密码加密、使用Passport进行身份验证和使用JWT进行令牌认证等。博客还提到了使用OpenAPI生成SDK和使用DayJS和Winston进行日志记录的实现。最后,博客介绍了数据迁移的相关命令和提交代码的规范。
博客的闪光点在于提供了一个完整的模板项目,涵盖了NestJS和PostgreSQL的常见功能和实现,使读者能够更快地开始一个新的项目。此外,博客还提供了相关的提交记录和代码库链接,方便读者进一步学习和参考。
改进的空间包括以下几个方面:
总体而言,这篇博客提供了一个很好的NestJS和PostgreSQL的模板项目,为读者提供了一个快速开始的起点。希望作者能够进一步完善和扩展博客的内容,以帮助更多的读者学习和应用这个模板项目。