Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,5 @@
- Real tests for the Cats module: 9 tests covering both the service and the
controller with the repository mocked, where the previous specs were Nest CLI
stubs that could not resolve their own dependencies.
- `README_EN.md`, `CONTRIBUTING.md` and this changelog.
- `README_CN.md`, `CONTRIBUTING.md` and this changelog.
- `ci.yml`: lint, typecheck, test and build on Node 20 and 22.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Run `pnpm format` before committing so Prettier settles the formatting; CI does

- **Commit messages** follow [Conventional Commits](https://www.conventionalcommits.org/):
`fix:`, `feat:`, `docs:`, `chore:`, `refactor:`, `test:`.
- **Both READMEs.** If a change affects the documented API, update `README.md` _and_ `README_EN.md`.
- **Both READMEs.** If a change affects the documented API, update `README.md` _and_ `README_CN.md`.
- **Declare what you import.** Four packages were imported without ever appearing in
`package.json`; they only resolved through hoisting. pnpm's layout catches this, so do not work
around a missing module by reaching for a hoisted copy.
Expand Down Expand Up @@ -163,7 +163,7 @@ pnpm build # 编译到 dist/
## 一些约定

- **提交信息**遵循 [Conventional Commits](https://www.conventionalcommits.org/):`fix:`、`feat:`、`docs:`、`chore:`、`refactor:`、`test:`。
- **两份 README。** 如果改动影响了对外 API,请同时更新 `README.md` 和 `README_EN.md`。
- **两份 README。** 如果改动影响了对外 API,请同时更新 `README.md` 和 `README_CN.md`。
- **用了什么就声明什么。** 之前有四个包被 import 却从没出现在 `package.json` 里,靠依赖提升侥幸能跑。pnpm 的严格目录结构能抓到这类问题,请不要绕过去用提升出来的副本。
- **不要吞掉错误。** `catch (err) { return err }` 会把 `Error` 对象当成 200 响应发给客户端。让错误抛到 filter,由它转成文档里定义的结构。
- **entity 要和 DTO 保持一致。** entity 上缺的列,写入时会被静默丢弃。
Expand Down
249 changes: 92 additions & 157 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<h1 align="center">Nest-Postgre-API</h1>

<p align="center">
面向生产的 Node REST API 脚手架:Nest + PostgreSQL + TypeORM + JWT。
A production-ready Node REST API scaffold: Nest + PostgreSQL + TypeORM + JWT.
</p>

<p align="center">
Expand All @@ -12,214 +12,149 @@
<img src="https://img.shields.io/badge/pnpm-10-f69220?logo=pnpm&logoColor=white" alt="pnpm 10" />
<br />
<a href="https://github.com/ludejun/Nest-Postgre-API/actions/workflows/ci.yml"><img src="https://github.com/ludejun/Nest-Postgre-API/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
<a href="https://github.com/ludejun/Nest-Postgre-API/blob/master/CONTRIBUTING.md"><img src="https://img.shields.io/badge/PR-欢迎提交-brightgreen.svg" alt="欢迎 PR" /></a>
<a href="https://github.com/ludejun/Nest-Postgre-API/blob/master/CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome" /></a>
</p>

<p align="center">
<a href="./CHANGELOG.md">更新日志</a>
<a href="./CHANGELOG.md">Changelog</a>
·
<a href="./CONTRIBUTING.md">贡献指南</a>
<a href="./CONTRIBUTING.md">Contributing</a>
·
<a href="./README_EN.md">English</a>
<a href="./README_CN.md">中文文档</a>
</p>

---

_Nest + PostgreSQL + JWT + TypeORM_ —— 基于 Nest 和传统数据库 PostgreSQL 的 Node RESTful API 脚手架,适用于基础生产项目。
A RESTful API scaffold built on Nest and PostgreSQL, meant as the starting point for a real
project rather than a toy.

参考 https://github.com/surmon-china/nodepress
Structure and conventions follow [nodepress](https://github.com/surmon-china/nodepress). TypeORM
takes some getting used to if you are coming from raw SQL, but it pays off: it integrates natively
with Nest, entities line up with the tables, and the query API stays readable —
see the [find options](https://typeorm.io/find-options).

刚开始不习惯 TypeORM,想使用传统 SQL 来请求数据库,后面使用 TypeORM 也还习惯:原生兼容 Nest、entity 可以和数据库打通、API 也还简单。查询参考 https://typeorm.biunav.com/zh/find-options.html
Server port, database credentials and auth settings all live in `src/app.config.ts`.

服务器端口及数据库配置、Auth 配置等详见 `src/app.config.ts`。
## Requirements

## 环境要求
Node >= 20, PostgreSQL, and [pnpm](https://pnpm.io/).

Node >= 20,PostgreSQL,以及 [pnpm](https://pnpm.io/)。

## 命令
## Commands

```shell
pnpm install
pnpm start:dev # 开发环境,watch 模式
pnpm build # 编译到 dist/
pnpm start:prod # 运行编译产物
pnpm start:dev # development, watch mode
pnpm build # compile to dist/
pnpm start:prod # run the compiled output

pnpm lint # eslint
pnpm typecheck # tsc --noEmit
pnpm test # vitest(单元测试)
pnpm test:e2e # vitest(e2e,需要可用的数据库)
pnpm test # vitest (unit)
pnpm test:e2e # vitest (e2e — needs a reachable database)
pnpm format # prettier --write
```

## 接口概述

- HTTP 状态码(详见 [errors] )

- `400` 请求的业务被拒绝
- `401` 鉴权失败
- `403` 权限不足/请求参数需要更高的权限
- `404` 资源不存在
- `405` 无此方法
- `500` 服务器挂了
- `200` 正常
- `201` POST 正常

- 数据特征码(详见 [http.interface.ts] )
- `status`:
- `success`:正常
- `error`:异常
- `message`:永远返回(由 [http.decorator] 装饰)
- `error`:一般会返回错误发生节点的 error;在 `status` 为 `error` 的时候必须返回,方便调试
- `debug`:开发模式下为发生错误的堆栈,生产模式不返回
- `result`:在 `status` 为 `success` 的时候必须返回
- 列表数据:一般返回`{ pagination: {...}, data: {..} }`
- 具体数据:例如文章,则包含直接数据如`{ title: '', content: ... }`

### 数据模型

- 通用

略,采用传统关系数据库模型

### 应用结构

- 入口

- `main.ts`:引入配置,启动主程序,引入各种全局服务
- `app.module.ts`:主程序根模块,负责各业务模块的聚合
- `app.controller.ts`:主程序根控制器
- `app.config.ts`:主程序配置,数据库、程序、第三方,一切可配置项
- `app.environment.ts:`全局环境变量

- 请求处理流程
## API conventions

1. `request`:收到请求
2. `middleware`:中间件过滤(跨域、来源校验等处理)
3. `guard`:守卫过滤(鉴权)
4. `interceptor:before`:数据流拦截器(本应用为空,即:无处理)
5. `pipe`:参数提取(校验)器
6. `controller`:业务控制器
7. `service`:业务服务
8. `interceptor:after`:数据流拦截器(格式化数据、错误)
9. `filter`:捕获以上所有流程中出现的异常,如果任何一个环节抛出异常,则返回错误
**HTTP status codes** (see `src/errors/`):

- 鉴权处理流程
| | |
| --- | --- |
| `200` | OK |
| `201` | Created (POST) |
| `400` | The request was understood but rejected by the business rules |
| `401` | Authentication failed |
| `403` | Insufficient permissions for the request or its parameters |
| `404` | No such resource |
| `405` | No such method |
| `500` | Server error |

1. `guard`:[守卫] 分析请求
2. `guard.canActivate`:继承处理
3. `JwtStrategy.validate`:调用 [鉴权服务]
4. `guard.handleRequest`:[根据鉴权服务返回的结果作判断处理,通行或拦截]
**Response shape** (see `src/interfaces/http.interface.ts`):

- 鉴权级别
- `status` — `success` or `error`.
- `message` — always present, added by the `@HttpProcessor` decorator.
- `error` — the error from wherever it occurred. Required whenever `status` is `error`, so failures
are debuggable.
- `debug` — the stack trace, in development only.
- `result` — required whenever `status` is `success`.
- list responses: `{ pagination: {...}, data: [...] }`
- single resources: the resource itself, e.g. `{ title: '', content: ... }`

- 任何高级操作(CUD)都会校验必须的 Token(代码见 [auth.guard.ts] )
- 涉及表数据读取的 GET 请求会智能校验 Token,无 Token 或 Token 验证生效则通行,否则不通行(代码见 [humanized-auth.guard.ts] )
## Application structure

- 参数校验逻辑(代码见 [query-params.decorator.ts] )
**Entry points**

- 普通用户使用高级查询参数将被视为无权限,返回 403
- 任何用户的请求参数不合法,将被校验器拦截,返回 400
| File | Role |
| --- | --- |
| `main.ts` | Loads config, starts the app, registers the global services |
| `app.module.ts` | Root module; composes the feature modules |
| `app.controller.ts` | Root controller |
| `app.config.ts` | Everything configurable: database, app, third parties |
| `app.environment.ts` | Global environment flags |

- 错误过滤器(代码见 [error.filter.ts] )
**Request pipeline**

- 拦截器 [interceptors]
1. `request` — the request arrives
2. `middleware` — CORS and origin checks
3. `guard` — authentication
4. `interceptor:before` — inbound stream (empty in this app)
5. `pipe` — parameter extraction and validation
6. `controller` — the route handler
7. `service` — the business logic
8. `interceptor:after` — response and error formatting
9. `filter` — catches anything thrown anywhere above and turns it into an error response

- [缓存拦截器]:自定义这个拦截器是是要弥补框架不支持 ttl 参数的缺陷
- [数据流转换拦截器]:当控制器所需的 Promise service 成功响应时,将在此被转换为标准的数据结构
- [数据流异常拦截器]:当控制器所需的 Promise service 发生错误时,错误将在此被捕获
- [日志拦截器]:代替默认的全局日志
**Authentication flow**

- 装饰器扩展 [decorators]
1. `guard` inspects the request
2. `guard.canActivate` runs the inherited handling
3. `JwtStrategy.validate` calls the auth service
4. `guard.handleRequest` lets the request through, or blocks it

- [缓存装饰器]:用于配置 `cache key / cache ttl`
- [控制器响应装饰器]:用于输出规范化的信息,如 `message` 和 翻页参数数据
- [请求参数提取器]:用户自动校验和格式化请求参数,包括 `query/params/辅助信息`
**Authorisation levels**

- 守卫 [guards]
- Every mutating operation (create / update / delete) requires a valid token — see
`src/guards/auth.guard.ts`.
- GET requests that read table data validate the token opportunistically: no token, or a valid
token, both pass; an invalid token does not.

- 默认所有非 GET 请求会使用 [Auth] 守卫鉴权
- 所有涉及到多角色请求的 GET 接口会使用 [HumanizedJwtAuthGuard] 进行鉴权
**Parameter validation** (`src/decorators/query-params.decorator.ts`)

- 中间件 [middlewares]
- An ordinary user reaching for an advanced query parameter is treated as unauthorised → `403`.
- Any request with invalid parameters is stopped by the validator → `400`.

- [Cors 中间件],用于处理跨域访问
- [Origin 中间件],用于拦截各路不明请求
**Interceptors** (`src/interceptors/`)

- 管道 [pipes]
- *transform* — turns a successful service Promise into the standard response shape.
- *error* — catches a failing service Promise.
- *logging* — replaces the default global logger.

- validation.pipe 用于验证所有基于 class-validate 的验证类
**Decorators** (`src/decorators/`)

- 业务模块 [modules]
- *controller response* — normalises the output, including `message` and pagination.
- *query params* — validates and formats `query` / `params` and the derived metadata.

- Cats Demo
**Guards** (`src/guards/`) — every non-GET request goes through the `Auth` guard.

- 核心辅助模块 [processors]
- [数据库]
<!-- - 连接数据库和异常自动重试
- [缓存 / Redis]
- 基本的缓存数据 Set、Get
- 扩展的 [Promise 工作模式](https://github.com/surmon-china/nodepress/blob/main/src/processors/cache/cache.service.ts#L99)(双向同步/被动更新)
- 扩展的 [Interval 工作模式](https://github.com/surmon-china/nodepress/blob/main/src/processors/cache/cache.service.ts#L138)(超时更新/定时更新)
- [辅助 / Helper](https://github.com/surmon-china/nodepress/blob/main/src/processors/helper)
- [搜索引擎实时更新服务](https://github.com/surmon-china/nodepress/blob/main/src/processors/helper/helper.service.seo.ts):根据入参主动提交搜索引擎收录,支持百度、Google 服务;分别会在动态数据 进行 CUD 的时候调用对应方法
- [评论过滤服务](https://github.com/surmon-china/nodepress/blob/main/src/processors/helper/helper.service.akismet.ts):使用 akismet 过滤 spam;暴露三个方法:校验 spam、提交 spam、提交 ham
- [邮件服务](https://github.com/surmon-china/nodepress/blob/main/src/processors/helper/helper.service.email.ts):根据入参发送邮件;程序启动时会自动校验客户端有效性,校验成功则根据入参发送邮件
- [IP 地理查询服务](https://github.com/surmon-china/nodepress/blob/main/src/processors/helper/helper.service.ip.ts):根据入参查询 IP 物理位置;控制器内优先使用阿里云 IP 查询服务,当服务无效,~~使用本地 GEO 库查询~~,使用 ip.cn 等备用方案
- [第三方云存储服务](https://github.com/surmon-china/nodepress/blob/main/src/processors/helper/helper.service.oss.ts):生成云存储上传 Token(目前服务为 Aliyun OSS),后期可以添加 SDK 的更多支持,比如管理文件
- Google 证书(鉴权)服务:用于生成各 Google 应用的服务端证书 -->
**Middleware** (`src/middlewares/`) — CORS handling and origin filtering.

**Pipes** (`src/pipes/`) — `validation.pipe` validates everything built on class-validator.

#### Google Indexing API
**Feature modules** (`src/modules/`) — an `Auth` module and a `Cats` demo.

- [完整的配置流程文档](https://developers.google.com/search/apis/indexing-api/v3/quickstart)
- 「 统计用户的所有者角色 」添加页面 [在这里](https://www.google.com/webmains/verification/details?hl=zh-CN&domain=<xxx.com>),而非 [新版的](https://search.google.com/search-console/users?resource_id=<xxx.com>)
**Core processors** (`src/processors/`)

#### Google Auth
- *database* — the TypeORM connection.
- *helper* — email, IP geolocation, SEO submission and Google credentials services.

- OAuth 2.0 客户端 ID、服务帐号密钥 都是 OAuth 授权类型
- [Auth 申请及管理页面](https://console.developers.google.com/apis/credentials)

#### Google Analytics Embed API

- [完整文档](https://developers.google.com/analytics/devguides/reporting/embed/v1/)
- [完整示例](https://ga-dev-tools.appspot.com/embed-api/)
- [服务端签发 token 鉴权示例](https://ga-dev-tools.appspot.com/embed-api/server-side-authorization/)
- [客户端 API 文档](https://developers.google.com/analytics/devguides/reporting/embed/v1/core-methods-reference)
- [将服务账户添加为 GA 的数据阅读者操作页面](https://marketingplatform.google.com/home/accounts)

## Development Setup

```bash
# 安装
$ yarn

# 开发
$ yarn start:dev

# 测试
$ yarn lint
$ yarn test
$ yarn test:e2e
$ yarn test:cov
$ yarn test:watch

# 构建
$ yarn build

# 生产环境运行
$ yarn start:prod

# 更新 GEO IP 库数据
$ yarn update-geo-db
```
### Google Indexing API

打开http://localhost:8000 即可看到数据返回
[Full setup guide](https://developers.google.com/search/apis/indexing-api/v3/quickstart)

## 参与贡献
## Contributing

见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
See [CONTRIBUTING.md](./CONTRIBUTING.md).

## 开源协议
## License

[MIT](./LICENSE)
Loading
Loading