集成koa2+ts+typeorm记录
集成koa2+ts+typeorm记录
- 1 使用koa-generator创建基础结构
- 2 转成ts并使用ES模块
- 3 引入typeorm
1 使用koa-generator创建基础结构
koa的官网?
提前安装koa脚手架
npm install -g koa-generator
创建项目(使用ejs引擎)
koa2 project-name -e
导入依赖
pnpm i
2 转成ts并使用ES模块
安装必要依赖
npm install typescript ts-node @types/node -D
根据实际使用的中间件添加ts声明
pnpm i @types/koa @types/koa-bodyparser @types/koa-json
@types/koa-router -D
生成tsconfig.json
npx tsc --init
内容如下
{// TypeScript 配置文件,用于配置 TypeScript 编译器选项// 详细信息请访问 https://aka.ms/tsconfig"compilerOptions": {// 文件布局配置"rootDir": "./", // 指定 TypeScript 源代码根目录"outDir": "./dist", // 指定编译后 JavaScript 文件的输出目录// 编译环境设置// 模块系统设置,使用 nodenext 支持 Node.js 的 ES 模块// 详细信息请参考 https://aka.ms/tsconfig/module"module": "nodenext","moduleResolution": "nodenext",// 目标 JavaScript 版本,编译为最新的 ES 标准"target": "esnext",// 指定运行环境为 Node.js,包含 ES 最新特性库"lib": ["esnext"],// 包含 Node.js 的类型定义"types": ["node"],// 需要安装 @types/node 包提供 Node.js API 的类型定义支持// 其他输出选项"sourceMap": true, // 生成 source map 文件,便于调试"declaration": true, // 生成声明文件 (.d.ts)"declarationMap": true, // 为声明文件生成 source map// 更严格的类型检查选项"noUncheckedIndexedAccess": true, // 对索引签名访问启用严格检查"exactOptionalPropertyTypes": true, // 精确处理可选属性类型// 代码风格选项// "noImplicitReturns": true, // 要求函数所有路径都有返回值// "noImplicitOverride": true, // 要求明确标记 override 方法"noUnusedLocals": true, // 禁止声明未使用的局部变量"noUnusedParameters": true, // 禁止声明未使用的函数参数// "noFallthroughCasesInSwitch": true, // 禁止 switch 语句中的 fallthrough// "noPropertyAccessFromIndexSignature": true, // 禁止通过属性访问语法访问索引签名// 推荐的编译选项"strict": true, // 启用所有严格类型检查选项"jsx": "react-jsx", // 使用 React JSX 转换"verbatimModuleSyntax": true, // 保持模块导入/导出语法的原样"isolatedModules": true, // 确保每个文件可以单独编译"noUncheckedSideEffectImports": true, // 禁止未检查的副作用导入"moduleDetection": "force", // 强制模块检测模式"skipLibCheck": true, // 跳过声明文件的类型检查,提高编译速度"esModuleInterop": true, // 启用 ES 模块互操作性"allowSyntheticDefaultImports": true, // 允许合成默认导入"noImplicitAny": false // 禁止隐式 any 类型(设置为 false 以允许隐式 any)},"include": ["**/*.ts"],"exclude": ["node_modules", "dist"],"ts-node": {"esm": true,"experimentalSpecifierResolution": "node"}
}
修改自动生成的文件
app.js => app.ts
import Koa from "koa";
import views from "koa-views";
import json from "koa-json";
import onerror from "koa-onerror";
import bodyparser from "koa-bodyparser";
import logger from "koa-logger";
import type { Context, Next } from "koa";
import serve from "koa-static";
import { resolve } from "path";
const app = new Koa();
// error handler
onerror(app);
// middlewares
app.use(bodyparser({enableTypes: ["json", "form", "text"],})
);
app.use(json({}));
app.use(logger());
app.use(serve(resolve("./public")));
app.use(views(resolve("./src/views"), {extension: "ejs",})
);
// logger
app.use(async (ctx: Context, next: Next) => {const start = Date.now();await next();const ms = Date.now() - start;console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// routes
import index from "./src/routes/index.ts";
import users from "./src/routes/users.ts";
app.use(index.routes());
app.use(users.routes());
// 后续使用自动化
// import { registerRoutes } from "./src/utils/register.js";
// await registerRoutes(app, resolve("./src/routes"));export default app;
启动文件
www => www.ts
#!/usr/bin/env node
import app from "../app.ts";
import http from "http";
import debug from "debug";var port = normalizePort(process.env.PORT || '3000');
var server = http.createServer(app.callback());server.listen(port,()=>{console.log(`🚀 服务器已启动: http://localhost:${port}`);console.log(`🕒 ${new Date().toLocaleString()}`);
});
server.on('error', onError);
server.on('listening', onListening);function normalizePort(val) {var port = parseInt(val, 10);if (isNaN(port)) {// named pipereturn val;}if (port >= 0) {// port numberreturn port;}return false;
}function onError(error) {if (error.syscall !== 'listen') {throw error;}var bind = typeof port === 'string'? 'Pipe ' + port: 'Port ' + port;// handle specific listen errors with friendly messagesswitch (error.code) {case 'EACCES':console.error(bind + ' requires elevated privileges');process.exit(1);break;case 'EADDRINUSE':console.error(bind + ' is already in use');process.exit(1);break;default:throw error;}
}function onListening() {var addr = server.address();var bind = typeof addr === 'string'? 'pipe ' + addr: 'port ' + addr?.port;debug('Listening on ' + bind);
}
配置文件package.json
{"name": "koa2-typeorm-learn","version": "0.1.0","private": true,"type": "module","scripts": {"build": "tsc","start": "node dist/bin/www","dev": "nodemon --watch './**/*' -e ts,tsx --exec ts-node ./bin/www.ts","prd": "pm2 start dist/bin/www","test": "echo \"Error: no test specified\" && exit 1","debug": "nodemon --inspect --watch './**/*' -e ts,tsx --exec ts-node ./bin/www.ts"},"dependencies": {"debug": "^4.1.1","ejs": "~2.3.3","koa": "^2.7.0","koa-bodyparser": "^4.2.1","koa-convert": "^1.2.0","koa-json": "^2.0.2","koa-logger": "^3.2.0","koa-onerror": "^4.1.0","koa-router": "^7.4.0","koa-static": "^5.0.0","koa-views": "^6.2.0"},"devDependencies": {"@types/debug": "^4.1.12","@types/koa": "^3.0.0","@types/koa-bodyparser": "^4.3.12","@types/koa-json": "^2.0.23","@types/koa-logger": "^3.1.5","@types/koa-router": "^7.4.8","@types/koa-static": "^4.0.4","@types/koa-views": "^7.0.0","@types/node": "^24.2.1","nodemon": "^1.19.1","ts-node": "^10.9.2","typescript": "^5.9.2"}
}
其他文件举例示范
index.js => index.ts
import Router from "koa-router";
import type { Context, Next } from "koa";
const router = new Router();
router.get("/", async (ctx: Context, _next: Next) => {await ctx.render("index", {title: "Hello Koa 2!",});
});
router.get("/string", async (ctx: Context, _next: Next) => {ctx.body = "koa2 string";
});
router.get("/json", async (ctx: Context, _next: Next) => {ctx.body = {title: "koa2 json",};
});
export default router;
3 引入typeorm
中文文档
导入依赖
pnpm install typeorm reflect-metadata mysql2 -S
修改tsconfig.json
增加对于装饰器的支持
"emitDecoratorMetadata": true, // 允许装饰器元数据的生成
"experimentalDecorators": true // 启用实验性的装饰器支持
package.json增加scripts
"typeorm": "typeorm-ts-node-commonjs"
不推荐使用typeorm init生成配置,因为会重写一些文件
- gitigonor
- package.json
- tsconfig.json
全局安装typeorm-model-generator
以便根据数据库直接生成实体
npm i typeorm-model-generator -g
执行命令基于数据库生成实体
(不推荐使用配置文件的命令,因为我这是用它报错,并且一直解决不了)
typeorm-model-generator -h hostname -d database -p port -u username -x password -e engine -o outputDirectory
-h / --host: 数据库服务器的IP地址或域名。
-d / --database: 要连接的数据库名称。
-p / --port: 数据库服务器的端口号。
-u / --user: 登录数据库的用户名。
-x / --password: 数据库密码。
-e / --engine: 数据库引擎类型(如 mysql,)。
-o / --output-dir: 生成文件的目标目录。
给tsconfig.json增加属性,避免生成的实体报错
"strictPropertyInitialization": false, // 禁止未初始化的类属性(设置为 false 以允许未初始化)
创建数据源
import "reflect-metadata";
import { DataSource } from "typeorm";
import { TRole } from "../entity/TRole.ts";
export const AppDataSource = new DataSource({type: "mysql",host: "host",port: 3306,username: "db_admin",password: "password",database: "database",synchronize: true,logging: false,entities: [TRole],migrations: [],subscribers: [],
});
创建连通性测试方法
import { AppDataSource } from "../utils/dataSource.ts";
import { TRole } from "../entity/TRole.ts";
export const testConnectivity = async () => {await AppDataSource.initialize();const roles = await AppDataSource.manager.find(TRole);console.log("All roles from the db: ", roles);
};
在app.ts中导入并执行savedPhotos