第一个应用 #
一、Hello World #
让我们从最简单的Hello World开始,逐步了解Fastify的基本用法。
1.1 最简应用 #
创建 app.js:
javascript
const fastify = require('fastify')()
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
console.log(`Server listening on ${address}`)
})
运行:
bash
node app.js
测试:
bash
curl http://localhost:3000
输出:
json
{"hello":"world"}
1.2 带日志的应用 #
javascript
const fastify = require('fastify')({
logger: true
})
fastify.get('/', async (request, reply) => {
return { hello: 'world' }
})
fastify.listen({ port: 3000 }, (err, address) => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
日志输出:
text
{"level":30,"time":1709500000000,"msg":"Server listening at http://127.0.0.1:3000"}
{"level":30,"time":1709500000100,"reqId":"req-1","req":{"method":"GET","url":"/"},"msg":"incoming request"}
{"level":30,"time":1709500000105,"reqId":"req-1","res":{"statusCode":200},"responseTime":5,"msg":"request completed"}
二、基础路由 #
2.1 定义路由 #
Fastify支持所有HTTP方法:
javascript
const fastify = require('fastify')({ logger: true })
fastify.get('/users', async (request, reply) => {
return { method: 'GET', path: '/users' }
})
fastify.post('/users', async (request, reply) => {
return { method: 'POST', path: '/users', body: request.body }
})
fastify.put('/users/:id', async (request, reply) => {
return { method: 'PUT', path: '/users', id: request.params.id }
})
fastify.patch('/users/:id', async (request, reply) => {
return { method: 'PATCH', path: '/users', id: request.params.id }
})
fastify.delete('/users/:id', async (request, reply) => {
return { method: 'DELETE', path: '/users', id: request.params.id }
})
fastify.head('/users', async (request, reply) => {
reply.code(200).send()
})
fastify.options('/users', async (request, reply) => {
reply
.code(200)
.header('Allow', 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS')
.send()
})
fastify.listen({ port: 3000 })
2.2 路由选项 #
完整的路由定义:
javascript
fastify.route({
method: 'GET',
url: '/users/:id',
schema: {
params: {
type: 'object',
properties: {
id: { type: 'integer' }
}
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' }
}
}
}
},
preHandler: async (request, reply) => {
console.log('Pre-handler executed')
},
handler: async (request, reply) => {
return { id: request.params.id, name: 'John Doe' }
}
})
2.3 路由简写 #
Fastify提供了路由简写方法:
javascript
fastify.get('/path', options, handler)
fastify.post('/path', options, handler)
fastify.put('/path', options, handler)
fastify.patch('/path', options, handler)
fastify.delete('/path', options, handler)
fastify.head('/path', options, handler)
fastify.options('/path', options, handler)
fastify.all('/path', options, handler)
三、请求处理 #
3.1 获取请求参数 #
javascript
fastify.get('/users/:id', async (request, reply) => {
const { id } = request.params
return { userId: id }
})
fastify.get('/search', async (request, reply) => {
const { q, page, limit } = request.query
return { query: q, page, limit }
})
fastify.post('/users', async (request, reply) => {
const { name, email } = request.body
return { name, email }
})
fastify.get('/info', async (request, reply) => {
const userAgent = request.headers['user-agent']
const authorization = request.headers.authorization
return { userAgent, authorization }
})
3.2 参数验证 #
使用JSON Schema验证:
javascript
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string', minLength: 2 },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0, maximum: 150 }
}
}
}
}, async (request, reply) => {
return { success: true, data: request.body }
})
验证失败响应:
json
{
"statusCode": 400,
"error": "Bad Request",
"message": "body must have required property 'name'"
}
3.3 路由参数验证 #
javascript
fastify.get('/users/:id', {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'integer' }
}
}
}
}, async (request, reply) => {
return { userId: request.params.id }
})
fastify.get('/products/:category/:productId', {
schema: {
params: {
type: 'object',
required: ['category', 'productId'],
properties: {
category: { type: 'string' },
productId: { type: 'integer' }
}
}
}
}, async (request, reply) => {
const { category, productId } = request.params
return { category, productId }
})
四、响应处理 #
4.1 返回JSON #
javascript
fastify.get('/json', async (request, reply) => {
return { message: 'Hello JSON' }
})
fastify.get('/json-explicit', async (request, reply) => {
reply.send({ message: 'Hello JSON' })
})
4.2 设置状态码 #
javascript
fastify.get('/created', async (request, reply) => {
reply.code(201)
return { message: 'Created' }
})
fastify.get('/no-content', async (request, reply) => {
reply.code(204).send()
})
fastify.get('/not-found', async (request, reply) => {
reply.code(404)
return { error: 'Not Found' }
})
4.3 设置响应头 #
javascript
fastify.get('/headers', async (request, reply) => {
reply
.header('X-Custom-Header', 'value')
.header('Content-Type', 'application/json')
return { message: 'Headers set' }
})
fastify.get('/cache', async (request, reply) => {
reply
.header('Cache-Control', 'public, max-age=3600')
.header('ETag', 'abc123')
return { data: 'cached data' }
})
4.4 Cookie操作 #
javascript
fastify.register(require('@fastify/cookie'))
fastify.get('/set-cookie', async (request, reply) => {
reply.setCookie('sessionId', 'abc123', {
httpOnly: true,
secure: true,
maxAge: 3600
})
return { message: 'Cookie set' }
})
fastify.get('/get-cookie', async (request, reply) => {
const sessionId = request.cookies.sessionId
return { sessionId }
})
4.5 重定向 #
javascript
fastify.get('/redirect', async (request, reply) => {
reply.redirect('/target')
})
fastify.get('/redirect-external', async (request, reply) => {
reply.redirect('https://example.com')
})
fastify.get('/redirect-301', async (request, reply) => {
reply.redirect(301, '/new-location')
})
五、错误处理 #
5.1 抛出错误 #
javascript
fastify.get('/error', async (request, reply) => {
throw fastify.httpErrors.notFound('User not found')
})
fastify.get('/error-custom', async (request, reply) => {
const error = new Error('Something went wrong')
error.statusCode = 500
throw error
})
5.2 使用HTTP错误 #
javascript
fastify.register(require('@fastify/sensible'))
fastify.get('/not-found', async (request, reply) => {
throw fastify.httpErrors.notFound()
})
fastify.get('/bad-request', async (request, reply) => {
throw fastify.httpErrors.badRequest('Invalid parameters')
})
fastify.get('/unauthorized', async (request, reply) => {
throw fastify.httpErrors.unauthorized('Token required')
})
fastify.get('/forbidden', async (request, reply) => {
throw fastify.httpErrors.forbidden()
})
5.3 自定义错误处理 #
javascript
fastify.setErrorHandler((error, request, reply) => {
if (error.validation) {
reply.code(400).send({
statusCode: 400,
error: 'Validation Error',
message: error.message
})
return
}
reply.code(error.statusCode || 500).send({
statusCode: error.statusCode || 500,
error: error.name,
message: error.message
})
})
六、异步处理 #
6.1 async/await #
javascript
fastify.get('/async', async (request, reply) => {
const data = await fetchData()
return data
})
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve({ data: 'fetched' })
}, 100)
})
}
6.2 Promise #
javascript
fastify.get('/promise', (request, reply) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ data: 'from promise' })
}, 100)
})
})
6.3 流式响应 #
javascript
fastify.get('/stream', (request, reply) => {
const { Readable } = require('stream')
const stream = new Readable({
read() {}
})
stream.push('Hello ')
stream.push('World!')
stream.push(null)
reply.type('text/plain').send(stream)
})
七、完整示例 #
7.1 用户API示例 #
javascript
const fastify = require('fastify')({ logger: true })
let users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
]
fastify.get('/users', async (request, reply) => {
return users
})
fastify.get('/users/:id', {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'integer' }
}
}
}
}, async (request, reply) => {
const user = users.find(u => u.id === request.params.id)
if (!user) {
throw fastify.httpErrors.notFound('User not found')
}
return user
})
fastify.post('/users', {
schema: {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' }
}
}
}
}, async (request, reply) => {
const newUser = {
id: users.length + 1,
...request.body
}
users.push(newUser)
reply.code(201)
return newUser
})
fastify.put('/users/:id', {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'integer' }
}
},
body: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' }
}
}
}
}, async (request, reply) => {
const index = users.findIndex(u => u.id === request.params.id)
if (index === -1) {
throw fastify.httpErrors.notFound('User not found')
}
users[index] = { ...users[index], ...request.body }
return users[index]
})
fastify.delete('/users/:id', async (request, reply) => {
const index = users.findIndex(u => u.id === request.params.id)
if (index === -1) {
throw fastify.httpErrors.notFound('User not found')
}
users.splice(index, 1)
reply.code(204).send()
})
fastify.listen({ port: 3000 })
八、测试应用 #
8.1 使用curl测试 #
bash
curl http://localhost:3000/users
curl http://localhost:3000/users/1
curl -X POST -H "Content-Type: application/json" -d '{"name":"Charlie","email":"charlie@example.com"}' http://localhost:3000/users
curl -X PUT -H "Content-Type: application/json" -d '{"name":"Alice Updated"}' http://localhost:3000/users/1
curl -X DELETE http://localhost:3000/users/1
8.2 使用Node.js测试 #
javascript
const { test } = require('node:test')
const assert = require('node:assert')
const Fastify = require('fastify')
const app = require('./app')
test('GET /users', async (t) => {
const fastify = Fastify()
fastify.register(app)
const response = await fastify.inject({
method: 'GET',
url: '/users'
})
assert.strictEqual(response.statusCode, 200)
assert.ok(Array.isArray(JSON.parse(response.payload)))
})
九、总结 #
本章我们学习了:
- Hello World:最简单的Fastify应用
- 基础路由:GET、POST、PUT、DELETE等方法
- 请求处理:参数获取、验证
- 响应处理:JSON、状态码、响应头
- 错误处理:HTTP错误、自定义错误
- 异步处理:async/await、Promise、流
接下来让我们学习如何组织项目结构!
最后更新:2026-03-28