20 - Node.js + TypeScript
Node.js + TypeScript
项目初始化
# 创建项目
mkdir my-node-ts-app && cd my-node-ts-app
npm init -y
# 安装依赖
npm install -D typescript @types/node ts-node
# 初始化 tsconfig
npx tsc --init
tsconfig.json 推荐配置
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
package.json 配置
{
"name": "my-node-ts-app",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"typecheck": "tsc --noEmit"
}
}
ts-node
ts-node 可以直接运行 TypeScript 文件:
# 安装
npm install -D ts-node
# 运行单个文件
npx ts-node src/index.ts
# 使用 tsconfig-paths 支持路径别名
npm install -D tsconfig-paths
npx ts-node -r tsconfig-paths/register src/index.ts
ts-node 配置
// tsconfig.json
{
"ts-node": {
"transpileOnly": true,
"compilerOptions": {
"module": "CommonJS"
}
},
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"esModuleInterop": true,
"strict": true
}
}
tsx(推荐)
tsx 是更现代的 TypeScript 运行时,基于 esbuild:
# 安装
npm install -D tsx
# 运行
npx tsx src/index.ts
# 监听模式
npx tsx watch src/index.ts
| 工具 | 速度 | ESM 支持 | 配置复杂度 |
|---|---|---|---|
| ts-node | 较慢 | 需配置 | 中等 |
| tsx | 极快 | 原生 | 简单 |
| tsc + node | 最慢 | 原生 | 简单 |
Node.js 内置类型
fs 模块
import fs from "node:fs";
import fsPromises from "node:fs/promises";
// 同步读取
const content: string = fs.readFileSync("./data.txt", "utf-8");
// 异步读取
const data: string = await fsPromises.readFile("./data.txt", "utf-8");
// 写入文件
await fsPromises.writeFile("./output.txt", "Hello", "utf-8");
// 检查文件存在
const exists: boolean = fs.existsSync("./data.txt");
// 创建目录
await fsPromises.mkdir("./output", { recursive: true });
// 读取目录
const files: string[] = await fsPromises.readdir("./src");
// 文件信息
const stat: fs.Stats = await fsPromises.stat("./data.txt");
console.log(stat.size, stat.mtime);
path 模块
import path from "node:path";
const joined: string = path.join("src", "utils", "helpers.ts");
// src/utils/helpers.ts
const resolved: string = path.resolve("./src/index.ts");
// /absolute/path/to/src/index.ts
const ext: string = path.extname("file.ts"); // ".ts"
const base: string = path.basename("/path/to/file.ts"); // "file.ts"
const dir: string = path.dirname("/path/to/file.ts"); // "/path/to"
const parsed = path.parse("/path/to/file.ts");
// { root: '/', dir: '/path/to', base: 'file.ts', ext: '.ts', name: 'file' }
http 模块
import http from "node:http";
interface RequestHandler {
(req: http.IncomingMessage, res: http.ServerResponse): void;
}
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ message: "Hello" }));
});
server.listen(3000, () => {
console.log("Server running on port 3000");
});
process 和环境变量
// 环境变量类型
interface Env {
NODE_ENV: "development" | "production" | "test";
PORT: string;
DATABASE_URL: string;
API_KEY: string;
}
function getEnv(): Env {
const required = ["NODE_ENV", "PORT", "DATABASE_URL", "API_KEY"] as const;
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing environment variable: ${key}`);
}
}
return {
NODE_ENV: process.env.NODE_ENV as Env["NODE_ENV"],
PORT: process.env.PORT!,
DATABASE_URL: process.env.DATABASE_URL!,
API_KEY: process.env.API_KEY!
};
}
// 命令行参数
const args: string[] = process.argv.slice(2);
console.log("Arguments:", args);
Express + TypeScript
npm install express
npm install -D @types/express
import express, { Request, Response, NextFunction } from "express";
const app = express();
app.use(express.json());
// 路由参数类型
interface UserParams {
id: string;
}
interface UserBody {
name: string;
email: string;
}
interface UserQuery {
page?: string;
limit?: string;
}
// 路由处理
app.get("/api/users", (req: Request<{}, {}, {}, UserQuery>, res: Response) => {
const { page = "1", limit = "10" } = req.query;
res.json({ page: Number(page), limit: Number(limit) });
});
app.get("/api/users/:id", (req: Request<UserParams>, res: Response) => {
const { id } = req.params;
res.json({ id: Number(id) });
});
app.post("/api/users", (req: Request<{}, {}, UserBody>, res: Response) => {
const { name, email } = req.body;
res.json({ name, email });
});
// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).json({ error: err.message });
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});
类型安全的路由封装
import express, { Request, Response, Router } from "express";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
interface RouteConfig<
TParams = any,
TBody = any,
TQuery = any,
TResponse = any
> {
method: HttpMethod;
path: string;
handler: (
req: Request<TParams, TResponse, TBody, TQuery>,
res: Response<TResponse>
) => Promise<void> | void;
}
function createRouter(routes: RouteConfig[]): Router {
const router = express.Router();
for (const route of routes) {
const method = route.method.toLowerCase() as "get" | "post" | "put" | "delete";
router[method](route.path, route.handler);
}
return router;
}
// 使用
const userRoutes = createRouter([
{
method: "GET",
path: "/",
handler: async (req, res) => {
res.json([]);
}
},
{
method: "POST",
path: "/",
handler: async (req, res) => {
res.json({ id: 1, ...req.body });
}
}
]);
Fastify + TypeScript
npm install fastify
import Fastify from "fastify";
const fastify = Fastify({ logger: true });
// 路由 Schema(类型 + 验证)
fastify.post<{ Body: CreateUserDto }>("/api/users", {
schema: {
body: {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string" },
email: { type: "string", format: "email" }
}
}
}
}, async (request, reply) => {
const { name, email } = request.body;
return { id: 1, name, email };
});
interface CreateUserDto {
name: string;
email: string;
}
数据库操作
Prisma
npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
// model User {
// id Int @id @default(autoincrement())
// name String
// email String @unique
// posts Post[]
// }
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// 类型安全的数据库操作
async function getUsers() {
const users = await prisma.user.findMany({
include: { posts: true }
});
return users; // 自动推断类型
}
async function createUser(data: { name: string; email: string }) {
return prisma.user.create({ data });
}
类型安全的 SQL 查询
import Database from "better-sqlite3";
interface User {
id: number;
name: string;
email: string;
}
const db = new Database("app.db");
// 类型安全的查询
function getUser(id: number): User | undefined {
return db.prepare("SELECT * FROM users WHERE id = ?").get(id) as User | undefined;
}
function getAllUsers(): User[] {
return db.prepare("SELECT * FROM users").all() as User[];
}
function insertUser(user: Omit<User, "id">): number {
const result = db
.prepare("INSERT INTO users (name, email) VALUES (?, ?)")
.run(user.name, user.email);
return result.lastInsertRowid as number;
}
业务场景:API 服务骨架
import express from "express";
import { PrismaClient } from "@prisma/client";
// 配置
const config = {
port: Number(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || "development"
};
// 数据库
const prisma = new PrismaClient();
// 应用
const app = express();
app.use(express.json());
// 中间件类型
type AsyncHandler = (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => Promise<void>;
// 异步包装器
function asyncHandler(fn: AsyncHandler): express.RequestHandler {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
}
// 路由
app.get(
"/api/users",
asyncHandler(async (req, res) => {
const users = await prisma.user.findMany();
res.json(users);
})
);
app.get(
"/api/users/:id",
asyncHandler(async (req, res) => {
const user = await prisma.user.findUnique({
where: { id: Number(req.params.id) }
});
if (!user) {
res.status(404).json({ error: "User not found" });
return;
}
res.json(user);
})
);
// 全局错误处理
app.use((err: Error, req: express.Request, res: express.Response, _next: express.NextFunction) => {
console.error(err);
res.status(500).json({
error: config.nodeEnv === "production" ? "Internal Server Error" : err.message
});
});
// 启动
app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
});
注意事项
@types/node——安装 Node.js 的类型定义- ESM vs CJS——根据项目需求选择模块系统
- tsx 比 ts-node 更快——推荐使用 tsx 作为开发运行时
- 环境变量类型——创建类型定义确保环境变量存在
- 数据库类型——使用 Prisma 等 ORM 可以获得类型安全的数据库操作