typescript
TypeScript + Node.js 后端实践
By AI-Writer 18 min read
TypeScript + Node.js 后端实践
TypeScript 在 Node.js 后端开发中能显著提升代码质量与可维护性。本文涵盖类型定义、环境变量、主流框架集成与 ORM 类型安全等核心实践。
Node.js 内置类型
安装类型定义
bash
npm install --save-dev @types/nodeprocess.env 类型化
typescript
// env.ts
import 'dotenv/config'
// 定义环境变量接口
interface Env {
NODE_ENV: 'development' | 'production' | 'test'
PORT: string
DATABASE_URL: string
JWT_SECRET: string
JWT_EXPIRES_IN: string
REDIS_URL: string
}
// 通过 ProcessEnv 扩展
declare global {
namespace NodeJS {
interface ProcessEnv extends Env {}
}
}
// 安全地访问环境变量
const config = {
nodeEnv: process.env.NODE_ENV ?? 'development',
port: parseInt(process.env.PORT ?? '3000', 10),
isProduction: process.env.NODE_ENV === 'production'
}
export { config }module 类型
json
// tsconfig.json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}typescript
// utils/logger.ts
export function formatDate(date: Date): string {
return date.toISOString()
}
// 调用方
import { formatDate } from './utils/logger.js'Express 类型扩展
基本类型
typescript
// app.ts
import express, { Request, Response, NextFunction } from 'express'
const app = express()
// Request 泛型参数:params、query、body、headers
app.get('/users/:id', (req: Request, res: Response) => {
const userId = req.params.id // string
res.json({ userId })
})
// 带类型的 Request
interface UserParams { id: string }
interface UserQuery { page?: string; limit?: string }
app.get<UserParams, any, any, UserQuery>(
'/users/:id',
(req, res) => {
const { id } = req.params // string
const { page = '1', limit = '10' } = req.query // string | undefined
}
)响应类型
typescript
import { Request, Response } from 'express'
// 统一的 API 响应类型
interface ApiResponse<T> {
success: boolean
data?: T
error?: string
timestamp: string
}
function success<T>(res: Response, data: T): Response {
return res.json({
success: true,
data,
timestamp: new Date().toISOString()
} as ApiResponse<T>)
}
function error(res: Response, message: string, status = 500): Response {
return res.status(status).json({
success: false,
error: message,
timestamp: new Date().toISOString()
} as ApiResponse<never>)
}
// 使用
app.get('/users/:id', async (req: Request, res: Response) => {
const user = await findUserById(req.params.id)
if (!user) return error(res, 'User not found', 404)
return success(res, user)
})中间件类型
typescript
// middleware/auth.ts
import { Request, Response, NextFunction } from 'express'
interface AuthenticatedRequest extends Request {
userId?: string
userRole?: 'admin' | 'user'
}
export function authMiddleware(
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): void {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
res.status(401).json({ error: 'Unauthorized' })
return
}
try {
const payload = verifyToken(token)
req.userId = payload.sub
req.userRole = payload.role
next()
} catch {
res.status(401).json({ error: 'Invalid token' })
}
}
// 使用中间件
app.get('/profile', authMiddleware, (req: AuthenticatedRequest, res) => {
res.json({ userId: req.userId, role: req.userRole })
})Koa 类型扩展
typescript
// app.ts
import Koa from 'koa'
import Router from '@koa/router'
const app = new Koa()
const router = new Router()
// 自定义上下文扩展
interface AppState {
userId?: string
}
interface AppContext extends Koa.DefaultState, AppState {}
interface AppBody extends Koa.DefaultState {}
app.use(async (ctx: Koa.Context, next: Koa.Next) => {
try {
await next()
} catch (err: any) {
ctx.status = err.status ?? 500
ctx.body = { error: err.message }
}
})
router.get('/health', (ctx) => {
ctx.body = { status: 'ok', timestamp: new Date().toISOString() }
})
app.use(router.routes())
app.listen(3000)数据校验:zod
zod 是 TypeScript 首选的数据校验库,与 TypeScript 类型深度集成:
typescript
import { z } from 'zod'
// 定义 schema,类型自动推断
const UserSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().int().positive().optional(),
role: z.enum(['admin', 'user', 'guest']).default('user')
})
// 推断 TypeScript 类型
type User = z.infer<typeof UserSchema>
// {
// name: string;
// email: string;
// age?: number;
// role: 'admin' | 'user' | 'guest';
// }
// 从现有类型推断 schema
const UserSchemaFromType = z.object({
name: z.string(),
email: z.string()
})
// 解析与校验
const result = UserSchema.safeParse({
name: 'Alice',
email: 'alice@example.com'
})
if (result.success) {
const user: User = result.data // ✅ 推断为 User 类型
} else {
console.error(result.error.issues)
}
// 异步校验(用于 API 请求)
app.post('/users', async (req, res) => {
const result = UserSchema.safeParse(req.body)
if (!result.success) {
res.status(400).json({ errors: result.error.issues })
return
}
const user = result.data // ✅ 类型安全
await createUser(user)
})zod 常用校验
typescript
// 嵌套对象
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
country: z.string()
})
const CompanySchema = z.object({
name: z.string(),
address: AddressSchema
})
// 数组
const IDsSchema = z.array(z.string().uuid()).min(1).max(100)
// 联合类型
const StatusSchema = z.union([
z.object({ type: z.literal('success'), data: z.record(z.any()) }),
z.object({ type: z.literal('error'), message: z.string() })
])
// 可辨识联合(推荐)
const EventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('userCreated'), userId: z.string() }),
z.object({ type: z.literal('userDeleted'), userId: z.string() }),
z.object({ type: z.literal('roleChanged'), userId: z.string(), role: z.string() })
])
type Event = z.infer<typeof EventSchema>ORM 类型安全:Drizzle ORM
Drizzle ORM 是 TypeScript 友好的轻量级 ORM:
typescript
// db/schema.ts
import { pgTable, serial, varchar, timestamp, boolean, integer } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
email: varchar('email', { length: 255 }).notNull().unique(),
passwordHash: varchar('password_hash').notNull(),
isAdmin: boolean('is_admin').default(false).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull()
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
userId: integer('user_id').references(() => users.id).notNull(),
title: varchar('title', { length: 255 }).notNull(),
content: varchar('content').notNull(),
publishedAt: timestamp('published_at'),
createdAt: timestamp('created_at').defaultNow().notNull()
})类型安全的查询
typescript
// db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres'
import pool from './pool'
import { users, posts } from './schema'
const db = drizzle(pool)
// 查询结果类型自动推断
const allUsers = await db.select().from(users)
// type: (typeof users.$inferSelect)[]
// 单行查询
const user = await db.select().from(users).where(eq(users.email, 'alice@example.com'))
// type: (typeof users.$inferSelect) | undefined
// 插入数据(类型安全)
const [newUser] = await db.insert(users).values({
name: 'Alice',
email: 'alice@example.com',
passwordHash: await hash('password123')
}).returning()
// type: typeof users.$inferInsert
// 更新数据
await db.update(users)
.set({ updatedAt: new Date() })
.where(eq(users.id, 1))关系查询
typescript
import { relations } from 'drizzle-orm'
// 定义关系
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts)
}))
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.userId],
references: [users.id]
})
}))
// 使用关系查询
const userWithPosts = await db.query.users.findFirst({
where: eq(users.email, 'alice@example.com'),
with: {
posts: {
where: isNotNull(posts.publishedAt),
orderBy: desc(posts.publishedAt)
}
}
})
// userWithPosts.posts 是完整的 posts 类型
userWithPosts?.posts.forEach(post => {
console.log(post.title) // ✅ 类型安全
})环境变量与配置
dotenv 配置
typescript
// config/index.ts
import 'dotenv/config'
import { z } from 'zod'
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.string().default('3000').transform(Number).pipe(z.number().positive()),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
CORS_ORIGIN: z.string().url()
})
const env = EnvSchema.parse(process.env)
export const config = {
nodeEnv: env.NODE_ENV,
port: env.PORT,
databaseUrl: env.DATABASE_URL,
jwtSecret: env.JWT_SECRET,
corsOrigin: env.CORS_ORIGIN
}总结
- 环境变量:使用 zod schema 定义和校验,
process.env自动类型化 - Express:使用
Request<Params, ResBody, ReqBody, Query>泛型指定参数类型 - Koa:扩展
AppState接口,为上下文添加自定义字段 - zod:schema 即类型定义,
safeParse返回类型安全的result.data - Drizzle ORM:schema 定义自动推断 insert/select 类型,query 结果类型化
- 关系:使用
relations定义表关系,.with()实现类型安全的关联查询
TypeScript 让 Node.js 后端代码同样拥有前端级别的类型安全保障。
#typescript
#nodejs
#express
#koa
#drizzle-orm
评论
A
Written by
AI-Writer
Related Articles
typescript
#13 TypeScript + Node.js 后端实践
深入讲解 Node.js 类型定义、Express/Koa 类型扩展、环境变量类型化、zod 数据校验与 drizzle-orm 类型安全等后端实践
Read More typescript
#8 映射类型与内置工具类型
深入讲解 TypeScript 映射类型的语法与修饰符,以及 Partial、Required、Readonly、Pick、Omit 等内置工具类型的实现原理
Read More