1. 路由器引擎
type Handler = (req: AppRequest, res: AppResponse) => Promise<void> | void
type Middleware = (req: AppRequest, res: AppResponse, next: () => Promise<void>) => Promise<void> | void
interface Route {
method: string
pattern: RegExp
paramNames: string[]
handler: Handler
}
class Router {
private routes: Route[] = []
private addRoute(method: string, path: string, handler: Handler) {
const paramNames: string[] = []
const pattern = new RegExp(
'^' + path.replace(/:(\w+)/g, (_, name) => {
paramNames.push(name)
return '([^/]+)'
}) + '$'
)
this.routes.push({ method, pattern, paramNames, handler })
}
get(path: string, handler: Handler) { this.addRoute('GET', path, handler) }
post(path: string, handler: Handler) { this.addRoute('POST', path, handler) }
put(path: string, handler: Handler) { this.addRoute('PUT', path, handler) }
delete(path: string, handler: Handler) { this.addRoute('DELETE', path, handler) }
match(method: string, url: string) {
for (const route of this.routes) {
if (route.method !== method) continue
const match = url.match(route.pattern)
if (match) {
const params: Record<string, string> = {}
route.paramNames.forEach((name, i) => params[name] = match[i + 1])
return { handler: route.handler, params }
}
}
return null
}
}
2. 中间件管道
class App {
private router = new Router()
private middlewares: Middleware[] = []
use(middleware: Middleware) {
this.middlewares.push(middleware)
}
private async runMiddleware(req: AppRequest, res: AppResponse, handler: Handler) {
let index = 0
const next = async (): Promise<void> => {
if (index < this.middlewares.length) {
const mw = this.middlewares[index++]
await mw(req, res, next)
} else {
await handler(req, res)
}
}
await next()
}
async handleRequest(req: IncomingMessage, res: ServerResponse) {
const url = new URL(req.url!, `http://${req.headers.host}`)
const appReq = new AppRequest(req, url)
const appRes = new AppResponse(res)
const matched = this.router.match(req.method!, url.pathname)
if (matched) {
appReq.params = matched.params
await this.runMiddleware(appReq, appRes, matched.handler)
} else {
appRes.status(404).json({ error: 'Not Found' })
}
}
listen(port: number) {
const server = createServer((req, res) => this.handleRequest(req, res))
server.listen(port, () => console.log(`Server on :${port}`))
}
}
3. 请求和响应助手
class AppRequest {
params: Record<string, string> = {}
query: Record<string, string>
private _body: unknown
constructor(public raw: IncomingMessage, public url: URL) {
this.query = Object.fromEntries(url.searchParams)
}
async body<T>(): Promise<T> {
if (this._body) return this._body as T
const chunks: Buffer[] = []
for await (const chunk of this.raw) chunks.push(chunk as Buffer)
const text = Buffer.concat(chunks).toString()
this._body = JSON.parse(text)
return this._body as T
}
get headers() { return this.raw.headers }
get method() { return this.raw.method! }
}
class AppResponse {
private statusCode = 200
constructor(private raw: ServerResponse) {}
status(code: number) { this.statusCode = code; return this }
json(data: unknown) {
this.raw.writeHead(this.statusCode, { 'Content-Type': 'application/json' })
this.raw.end(JSON.stringify(data))
}
send(text: string) {
this.raw.writeHead(this.statusCode, { 'Content-Type': 'text/plain' })
this.raw.end(text)
}
}
4. 使用框架
const app = new App()
// Middleware: logging
app.use(async (req, res, next) => {
const start = Date.now()
await next()
console.log(`${req.method} ${req.url.pathname} ${Date.now() - start}ms`)
})
// Middleware: CORS
app.use(async (req, res, next) => {
res.raw.setHeader('Access-Control-Allow-Origin', '*')
res.raw.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE')
await next()
})
// Routes
app.router.get('/api/users/:id', async (req, res) => {
const { id } = req.params
res.json({ id, name: 'Nguyen Van A' })
})
app.router.post('/api/users', async (req, res) => {
const body = await req.body<{ name: string }>()
res.status(201).json({ id: '1', name: body.name })
})
app.listen(3000)
下一篇: 資料庫驅動程式和連接池 — pg、mysql2、better-sqlite3。