tsoa基础使用

项目结构

目录结构大致如下

src/
├─ controllers/
│  └─ user.controller.ts
├─ routes/
│  └─ routes.ts          ← tsoa 生成
├─ services/
│  └─ user.service.ts
├─ app.ts

ORM框架生成基本信息

使用prisma依据数据库表结构,生成基本的实体(vo层)和操作数据库的操作(dao层)代码,这里不再多做结介绍!

安装和配置tsoa

安装tsoa

pnpm add tsoa @tsoa/runtime

根目录创建配置文件 tsoa.json

{
  "entryFile": "src/app.ts",  // 程序入口
  "noImplicitAdditionalProperties": "throw-on-extras",
  "controllerPathGlobs": ["src/controllers/*"], // 扫描controllers生成router
  "spec": {
    "outputDirectory": ".", // openApi文档生成路径
    "specVersion": 3
  },
  "routes": {
    "routesDir": "src/routers", // router生成路径
    "middleware": "koa" // 使用框架
  }
}

编写一个控制器

controller/user.controller.ts

import { queryOne } from "@/service/user";
import { user as User } from '@prisma/client';
import { Controller, Get, Path, Route, } from "tsoa";

@Route("user")
export class UserController extends Controller {
    @Get("{userId}")
    public async getUser(@Path() userId: number): Promise<User> {
        return queryOne({ id: userId });
    }
}

路由生成与使用

运行 npx tsoa routes,会根据你的配置路径生成 路由文件,比如我的在

src
  |--router
    |--routes.ts

在入口程序中使用它 app.ts

...
import Router from "@koa/router";
import { RegisterRoutes } from "@/router/routes";

const app = new Koa();
...
const router = new Router();
RegisterRoutes(router);
app.use(router.routes());

app.listen(3003, '0.0.0.0', () => {
  const { green, blue, bold } = chalk;
  const str = `${green('➜')}  ${bold('后端服务已启动:')}   ${blue('http://localhost:3003')}`;
  console.log(str);
});

openApi与swagger

运行 npx tsoa spec,会根据你的配置路径生成 openApi文档文件,比如我的在根目录下 swagger.json
如果你想在运行时获取,可以使用 generateXX

import {generateRoutes,generateSpec,ExtendedRoutesConfig,ExtendedSpecConfig} from "tsoa";

(async () => {
  const specOptions: ExtendedSpecConfig = {
    basePath: "/api",
    entryFile: "./app.ts",
    specVersion: 3,
    outputDirectory: ".",
    controllerPathGlobs: ["src/controllers/*"],
  };

  const routeOptions: ExtendedRoutesConfig = {
    basePath: "src/routers",
    entryFile: "./app.ts",
    routesDir: "./api",
  };

  await generateSpec(specOptions);

  await generateRoutes(routeOptions);
})();

其它

service

如上操作之后,你的router已经按照class的写法了,所以service层最好也统一一下吧,你也不想打破这份协调,对吧?!

services/user.service.ts

import { user as User, PrismaClient } from '@prisma/client';

// 给post请求(创建用户)用的类型定义,不应该包含 id.
export type UserCreationParams = Pick<User, "email" | "name" | "phoneNumbers">;

const prisma = new PrismaClient();
export class UsersService {
  public get(id: number): User {
     const result = await prisma.user.findFirst({where: { id }});
     return result;
  }

  public create(userCreationParams: UserCreationParams): User {
    return {
      id: Math.floor(Math.random() * 10000), // Random
      status: "Happy",
      ...userCreationParams,
    };
  }
}

同时生成

如果你既想生成路由,也想生成 openApi文档,可以

npx tsoa spec-and-routes

实时生成

tsx watch 只能「重启 Node 进程」,不会帮你重新执行 tsoa spec-and-routes
所以单纯 tsx watch src/index.ts 不会让路由 / OpenAPI 更新。

如果你需要监听controller里的文件变化,实时更新路由和openApi文档,可以使用 chokidar cli插件实时监听文件变化,安装之后可以这么用

"tsoa:gen": "tsoa spec-and-routes",
"tsoa:watch": "npx chokidar 'src/controllers/**' -c 'npm run tsoa:gen'"  // mac下

注意,在win下,单引号需要替换为双引号(建议这个,mac亦可用)

"tsoa:watch": "npx chokidar \"src/controllers/**\" -c \"npm run tsoa:gen\"" 

并行运行

我想程序启动的时候,先执行一遍 tsoa 生成,然后再监听改变持续生成,最后再启动项目。
因为涉及到多个同时执行程序,所以 靠&& 是不行的,因为它的作用是串行而非并行,
这个时候就用到了 concurrently,安装后使用

pacakge.json

  "scripts": {
    "dev_gen": "npm run clean && concurrently  \"npm run tsoa:gen\" \"npm run tsoa:watch\" \"npm run dev\"",
    "dev": "npx kill-port 3003 || true && tsx watch --clear-screen=false --include ./src/*.ts ./src/app.ts",
    "start": "npx kill-port 3003 && tsx ./src/app.ts",
    "prisma": "tsx ./bin/prisma.ts",
    "tsoa:gen": "tsoa spec-and-routes",
    "tsoa:watch": "npx chokidar \"src/controllers/**\" -c \"npm run tsoa:gen\"",
    "clean": "pkill -f chokidar || true && pkill -f tsx || true"
  },

允许 ts 装饰器

tsconfig.js 添加如下代码即可

{
  "compilerOptions": {
     ...
    /* 1. 允许在类及其成员上使用装饰器语法 */
    "experimentalDecorators": true,
    /* 2. 为装饰器发射类型元数据(如设计类型、参数类型、返回类型) */
    "emitDecoratorMetadata": true,
  }
}

Controller undefined

启动的时候报错


@Route("api")
export class RootController extends Controller {}

// 得到一下报错
Class extends value undefined is not a constructor or null

解决办法,在 tsconfig.json 添加如下配置即可

{
  "compilerOptions": {
    ...
    "paths": {
      ...
      "tsoa": ["node_modules/tsoa/dist"],
      "tsoa/": ["node_modules/tsoa/dist/"]
    }
  }
}

路径别名问题

tsx早在2024年就已经支持 tsconfig.json 中的路径别名,但是这并不适用用 tsoa

import { Controller, Get, Path, Route } from 'tsoa';
import JsonResult, { JsonResultType } from '@/utils/json-result';  

@Route('api')
export class RootController extends Controller {
  @Get()
  public async hi1(): Promise<JsonResultType<string>> {
    return JsonResult.success('hello, this is server');
  }
}

如上代码将报错,找不到 @/utils/json-result

enerate routes error.
[0]  GenerateMetadataError: No declarations found for referenced type JsonResultType.

解决办法很简单,不用别名即可

koaBody

在koa项目中,当你使用tsoa,并且控制器中使用到了 @Body接受参数的的时候,tsoa要求你必须使用koaBody插件,否则会报错 request body is required,其实这个早就有人发现了,并质疑作者为何不在官方文档中声明

import koaBody from 'koa-body';

app.use(koaBody());
posted @ 2025-08-05 00:31  丁少华  阅读(36)  评论(0)    收藏  举报