npm install express
npm install -D @types/express typescript @types/node ts-node{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist"
}
}import express, { Request, Response, NextFunction } from 'express'
const app = express()
app.get('/users', (req: Request, res: Response) => {
res.json({ users: [] })
})Express Router предоставляет дженерики для параметров:
// Request<Params, ResBody, ReqBody, Query>
interface UserParams {
id: string // всегда строка в Express params
}
app.get('/users/:id', (
req: Request<UserParams>,
res: Response
) => {
const userId = parseInt(req.params.id) // приводим к числу
res.json({ id: userId })
})interface CreateUserBody {
name: string
email: string
role?: 'admin' | 'user'
}
app.post('/users', (
req: Request<{}, {}, CreateUserBody>,
res: Response
) => {
const { name, email, role = 'user' } = req.body
// name: string, email: string — TypeScript доволен
res.status(201).json({ id: 1, name, email, role })
})interface UsersQuery {
page?: string
limit?: string
search?: string
role?: 'admin' | 'user'
}
app.get('/users', (
req: Request<{}, {}, {}, UsersQuery>,
res: Response
) => {
const page = parseInt(req.query.page || '1')
const limit = parseInt(req.query.limit || '10')
const search = req.query.search || ''
res.json({ page, limit, search })
})// Middleware: Request -> Response -> NextFunction
function logger(req: Request, res: Response, next: NextFunction): void {
console.log(`${req.method} ${req.path}`)
next()
}
// Middleware с ошибкой (4 параметра):
function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
): void {
console.error(err.stack)
res.status(500).json({ error: err.message })
}
app.use(logger)
app.use(errorHandler)// types/express.d.ts
declare module 'express-serve-static-core' {
interface Request {
user?: {
id: number
email: string
role: 'admin' | 'user'
}
startTime?: number
}
}// middleware/auth.ts
function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.split(' ')[1]
if (!token) return res.status(401).json({ error: 'Unauthorized' })
req.user = verifyToken(token) // TypeScript знает о req.user
next()
}// routes/users.ts
import { Router } from 'express'
const router = Router()
router.get('/', getUsers)
router.get('/:id', getUserById)
router.post('/', createUser)
router.put('/:id', updateUser)
router.delete('/:id', deleteUser)
export default routerПолный мини-фреймворк в стиле Express с типизированными роутами, middleware, body parsing и error handling
// Реализуем Express-подобный фреймворк в чистом JS.
// В TypeScript каждый роут имел бы строгие типы Request/Response.
// --- Express-подобный Application ---
class Application {
constructor() {
this._middleware = []
this._routes = []
this._errorHandlers = []
}
use(pathOrMiddleware, middleware) {
if (typeof pathOrMiddleware === 'function') {
// app.use(middleware)
this._middleware.push({ path: null, handler: pathOrMiddleware })
} else {
// app.use('/path', middleware)
this._middleware.push({ path: pathOrMiddleware, handler: middleware })
}
}
get(path, ...handlers) { this._addRoute('GET', path, handlers) }
post(path, ...handlers) { this._addRoute('POST', path, handlers) }
put(path, ...handlers) { this._addRoute('PUT', path, handlers) }
delete(path, ...handlers) { this._addRoute('DELETE', path, handlers) }
_addRoute(method, path, handlers) {
this._routes.push({ method, path, handlers })
}
_matchRoute(method, url) {
for (const route of this._routes) {
if (route.method !== method) continue
const params = this._extractParams(route.path, url)
if (params !== null) return { ...route, params }
}
return null
}
_extractParams(routePath, url) {
// /users/:id -> { id: '42' }
const routeParts = routePath.split('/')
const urlParts = url.split('?')[0].split('/')
if (routeParts.length !== urlParts.length) return null
const params = {}
for (let i = 0; i < routeParts.length; i++) {
if (routeParts[i].startsWith(':')) {
params[routeParts[i].slice(1)] = urlParts[i]
} else if (routeParts[i] !== urlParts[i]) {
return null
}
}
return params
}
_parseQuery(url) {
const queryStr = url.split('?')[1] || ''
const query = {}
queryStr.split('&').filter(Boolean).forEach(part => {
const [k, v] = part.split('=')
query[decodeURIComponent(k)] = decodeURIComponent(v || '')
})
return query
}
handle(method, url, body = null) {
const req = {
method,
url,
path: url.split('?')[0],
params: {},
query: this._parseQuery(url),
body: body || {},
headers: { authorization: null },
user: null, // расширяем через module augmentation в TS
}
const res = {
statusCode: 200,
_data: null,
_headers: {},
status(code) { this.statusCode = code; return this },
json(data) { this._data = data; return this },
send(text) { this._data = text; return this },
set(header, value) { this._headers[header] = value; return this },
}
const route = this._matchRoute(method, url)
if (route) req.params = route.params
const handlers = []
// Добавляем middleware
for (const mw of this._middleware) {
if (!mw.path || req.path.startsWith(mw.path)) {
handlers.push(mw.handler)
}
}
// Добавляем обработчики маршрута
if (route) {
handlers.push(...route.handlers)
} else {
handlers.push((req, res) => res.status(404).json({ error: 'Not Found' }))
}
// Запускаем цепочку
let idx = 0
const next = (err) => {
if (err) {
this._errorHandlers.forEach(h => h(err, req, res, () => {}))
return
}
if (idx < handlers.length) {
try {
handlers[idx++](req, res, next)
} catch (e) {
next(e)
}
}
}
next()
return { status: res.statusCode, body: res._data }
}
}
// --- Типизированные handlers (как в TS + Express) ---
// TS: interface User { id: number; name: string; role: 'admin' | 'user' }
const users = [
{ id: 1, name: 'Алексей', role: 'admin', email: 'alex@example.com' },
{ id: 2, name: 'Мария', role: 'user', email: 'maria@example.com' },
]
const app = new Application()
// Logger middleware
// TS: (req: Request, res: Response, next: NextFunction) => void
app.use((req, res, next) => {
console.log(`[LOG] ${req.method} ${req.url}`)
next()
})
// GET /users?role=admin&page=1
// TS: Request<{}, {}, {}, { role?: 'admin'|'user'; page?: string }>
app.get('/users', (req, res) => {
let result = [...users]
if (req.query.role) {
result = result.filter(u => u.role === req.query.role)
}
const page = parseInt(req.query.page || '1')
const limit = parseInt(req.query.limit || '10')
res.json({ users: result, page, total: result.length })
})
// GET /users/:id
// TS: Request<{ id: string }>
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id))
if (!user) return res.status(404).json({ error: 'User not found' })
res.json(user)
})
// POST /users
// TS: Request<{}, {}, { name: string; email: string; role?: 'admin'|'user' }>
app.post('/users', (req, res) => {
const { name, email, role = 'user' } = req.body
if (!name || !email) {
return res.status(400).json({ error: 'name and email required' })
}
const newUser = { id: users.length + 1, name, email, role }
users.push(newUser)
res.status(201).json(newUser)
})
// --- Демонстрация ---
console.log('=== GET /users ===')
let r = app.handle('GET', '/users')
console.log('status:', r.status, 'count:', r.body.users.length)
console.log('\n=== GET /users?role=admin ===')
r = app.handle('GET', '/users?role=admin')
console.log('status:', r.status, 'users:', r.body.users.map(u => u.name))
console.log('\n=== GET /users/2 ===')
r = app.handle('GET', '/users/2')
console.log('status:', r.status, 'user:', r.body.name)
console.log('\n=== GET /users/99 (not found) ===')
r = app.handle('GET', '/users/99')
console.log('status:', r.status, 'error:', r.body.error)
console.log('\n=== POST /users ===')
r = app.handle('POST', '/users', { name: 'Иван', email: 'ivan@example.com' })
console.log('status:', r.status, 'created:', r.body)
console.log('\n=== POST /users (validation error) ===')
r = app.handle('POST', '/users', { name: 'Нет email' })
console.log('status:', r.status, 'error:', r.body.error)npm install express
npm install -D @types/express typescript @types/node ts-node{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist"
}
}import express, { Request, Response, NextFunction } from 'express'
const app = express()
app.get('/users', (req: Request, res: Response) => {
res.json({ users: [] })
})Express Router предоставляет дженерики для параметров:
// Request<Params, ResBody, ReqBody, Query>
interface UserParams {
id: string // всегда строка в Express params
}
app.get('/users/:id', (
req: Request<UserParams>,
res: Response
) => {
const userId = parseInt(req.params.id) // приводим к числу
res.json({ id: userId })
})interface CreateUserBody {
name: string
email: string
role?: 'admin' | 'user'
}
app.post('/users', (
req: Request<{}, {}, CreateUserBody>,
res: Response
) => {
const { name, email, role = 'user' } = req.body
// name: string, email: string — TypeScript доволен
res.status(201).json({ id: 1, name, email, role })
})interface UsersQuery {
page?: string
limit?: string
search?: string
role?: 'admin' | 'user'
}
app.get('/users', (
req: Request<{}, {}, {}, UsersQuery>,
res: Response
) => {
const page = parseInt(req.query.page || '1')
const limit = parseInt(req.query.limit || '10')
const search = req.query.search || ''
res.json({ page, limit, search })
})// Middleware: Request -> Response -> NextFunction
function logger(req: Request, res: Response, next: NextFunction): void {
console.log(`${req.method} ${req.path}`)
next()
}
// Middleware с ошибкой (4 параметра):
function errorHandler(
err: Error,
req: Request,
res: Response,
next: NextFunction
): void {
console.error(err.stack)
res.status(500).json({ error: err.message })
}
app.use(logger)
app.use(errorHandler)// types/express.d.ts
declare module 'express-serve-static-core' {
interface Request {
user?: {
id: number
email: string
role: 'admin' | 'user'
}
startTime?: number
}
}// middleware/auth.ts
function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.split(' ')[1]
if (!token) return res.status(401).json({ error: 'Unauthorized' })
req.user = verifyToken(token) // TypeScript знает о req.user
next()
}// routes/users.ts
import { Router } from 'express'
const router = Router()
router.get('/', getUsers)
router.get('/:id', getUserById)
router.post('/', createUser)
router.put('/:id', updateUser)
router.delete('/:id', deleteUser)
export default routerПолный мини-фреймворк в стиле Express с типизированными роутами, middleware, body parsing и error handling
// Реализуем Express-подобный фреймворк в чистом JS.
// В TypeScript каждый роут имел бы строгие типы Request/Response.
// --- Express-подобный Application ---
class Application {
constructor() {
this._middleware = []
this._routes = []
this._errorHandlers = []
}
use(pathOrMiddleware, middleware) {
if (typeof pathOrMiddleware === 'function') {
// app.use(middleware)
this._middleware.push({ path: null, handler: pathOrMiddleware })
} else {
// app.use('/path', middleware)
this._middleware.push({ path: pathOrMiddleware, handler: middleware })
}
}
get(path, ...handlers) { this._addRoute('GET', path, handlers) }
post(path, ...handlers) { this._addRoute('POST', path, handlers) }
put(path, ...handlers) { this._addRoute('PUT', path, handlers) }
delete(path, ...handlers) { this._addRoute('DELETE', path, handlers) }
_addRoute(method, path, handlers) {
this._routes.push({ method, path, handlers })
}
_matchRoute(method, url) {
for (const route of this._routes) {
if (route.method !== method) continue
const params = this._extractParams(route.path, url)
if (params !== null) return { ...route, params }
}
return null
}
_extractParams(routePath, url) {
// /users/:id -> { id: '42' }
const routeParts = routePath.split('/')
const urlParts = url.split('?')[0].split('/')
if (routeParts.length !== urlParts.length) return null
const params = {}
for (let i = 0; i < routeParts.length; i++) {
if (routeParts[i].startsWith(':')) {
params[routeParts[i].slice(1)] = urlParts[i]
} else if (routeParts[i] !== urlParts[i]) {
return null
}
}
return params
}
_parseQuery(url) {
const queryStr = url.split('?')[1] || ''
const query = {}
queryStr.split('&').filter(Boolean).forEach(part => {
const [k, v] = part.split('=')
query[decodeURIComponent(k)] = decodeURIComponent(v || '')
})
return query
}
handle(method, url, body = null) {
const req = {
method,
url,
path: url.split('?')[0],
params: {},
query: this._parseQuery(url),
body: body || {},
headers: { authorization: null },
user: null, // расширяем через module augmentation в TS
}
const res = {
statusCode: 200,
_data: null,
_headers: {},
status(code) { this.statusCode = code; return this },
json(data) { this._data = data; return this },
send(text) { this._data = text; return this },
set(header, value) { this._headers[header] = value; return this },
}
const route = this._matchRoute(method, url)
if (route) req.params = route.params
const handlers = []
// Добавляем middleware
for (const mw of this._middleware) {
if (!mw.path || req.path.startsWith(mw.path)) {
handlers.push(mw.handler)
}
}
// Добавляем обработчики маршрута
if (route) {
handlers.push(...route.handlers)
} else {
handlers.push((req, res) => res.status(404).json({ error: 'Not Found' }))
}
// Запускаем цепочку
let idx = 0
const next = (err) => {
if (err) {
this._errorHandlers.forEach(h => h(err, req, res, () => {}))
return
}
if (idx < handlers.length) {
try {
handlers[idx++](req, res, next)
} catch (e) {
next(e)
}
}
}
next()
return { status: res.statusCode, body: res._data }
}
}
// --- Типизированные handlers (как в TS + Express) ---
// TS: interface User { id: number; name: string; role: 'admin' | 'user' }
const users = [
{ id: 1, name: 'Алексей', role: 'admin', email: 'alex@example.com' },
{ id: 2, name: 'Мария', role: 'user', email: 'maria@example.com' },
]
const app = new Application()
// Logger middleware
// TS: (req: Request, res: Response, next: NextFunction) => void
app.use((req, res, next) => {
console.log(`[LOG] ${req.method} ${req.url}`)
next()
})
// GET /users?role=admin&page=1
// TS: Request<{}, {}, {}, { role?: 'admin'|'user'; page?: string }>
app.get('/users', (req, res) => {
let result = [...users]
if (req.query.role) {
result = result.filter(u => u.role === req.query.role)
}
const page = parseInt(req.query.page || '1')
const limit = parseInt(req.query.limit || '10')
res.json({ users: result, page, total: result.length })
})
// GET /users/:id
// TS: Request<{ id: string }>
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id))
if (!user) return res.status(404).json({ error: 'User not found' })
res.json(user)
})
// POST /users
// TS: Request<{}, {}, { name: string; email: string; role?: 'admin'|'user' }>
app.post('/users', (req, res) => {
const { name, email, role = 'user' } = req.body
if (!name || !email) {
return res.status(400).json({ error: 'name and email required' })
}
const newUser = { id: users.length + 1, name, email, role }
users.push(newUser)
res.status(201).json(newUser)
})
// --- Демонстрация ---
console.log('=== GET /users ===')
let r = app.handle('GET', '/users')
console.log('status:', r.status, 'count:', r.body.users.length)
console.log('\n=== GET /users?role=admin ===')
r = app.handle('GET', '/users?role=admin')
console.log('status:', r.status, 'users:', r.body.users.map(u => u.name))
console.log('\n=== GET /users/2 ===')
r = app.handle('GET', '/users/2')
console.log('status:', r.status, 'user:', r.body.name)
console.log('\n=== GET /users/99 (not found) ===')
r = app.handle('GET', '/users/99')
console.log('status:', r.status, 'error:', r.body.error)
console.log('\n=== POST /users ===')
r = app.handle('POST', '/users', { name: 'Иван', email: 'ivan@example.com' })
console.log('status:', r.status, 'created:', r.body)
console.log('\n=== POST /users (validation error) ===')
r = app.handle('POST', '/users', { name: 'Нет email' })
console.log('status:', r.status, 'error:', r.body.error)Реализуй `createRouter()` — объект с методами `get`, `post`, `put`, `delete` для регистрации маршрутов и `handle(method, path, body)` для их обработки. Поддержи параметры маршрута (`:id`): они должны быть доступны в `req.params`. Каждый обработчик получает `req = { params, body, query }` и `res = { json(data), status(code) }`. `handle` возвращает `{ status, body }`.
matchPath: pathParts[i] — значение соответствующей части пути. addRoute: routes.push({ method, path, handler }). В handle: перебирай routes, вызывай matchPath, если params !== null — создай req/res и вызови handler.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке