RESTful API #

一、项目概述 #

1.1 项目结构 #

text
api-project/
├── src/
│   ├── index.js
│   ├── config/
│   │   └── index.js
│   ├── routes/
│   │   ├── index.js
│   │   ├── users.js
│   │   └── products.js
│   ├── handlers/
│   │   ├── users.js
│   │   └── products.js
│   ├── models/
│   │   ├── user.js
│   │   └── product.js
│   ├── services/
│   │   ├── userService.js
│   │   └── productService.js
│   ├── validations/
│   │   └── schemas.js
│   └── plugins/
│       ├── index.js
│       └── errorHandler.js
├── package.json
└── .env

1.2 安装依赖 #

bash
npm init -y
npm install @hapi/hapi @hapi/joi @hapi/boom @hapi/inert @hapi/vision
npm install dotenv
npm install -D nodemon

二、入口文件 #

2.1 src/index.js #

javascript
require('dotenv').config();
const Hapi = require('@hapi/hapi');
const config = require('./config');
const plugins = require('./plugins');
const routes = require('./routes');

const init = async () => {
    const server = Hapi.server({
        port: config.port,
        host: config.host,
        routes: {
            cors: {
                origin: ['*'],
                headers: ['Accept', 'Content-Type', 'Authorization']
            }
        }
    });

    await server.register(plugins);
    server.route(routes);

    await server.start();
    console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
    console.error(err);
    process.exit(1);
});

init();

2.2 src/config/index.js #

javascript
module.exports = {
    port: parseInt(process.env.PORT, 10) || 3000,
    host: process.env.HOST || 'localhost',
    env: process.env.NODE_ENV || 'development'
};

三、数据模型 #

3.1 src/models/user.js #

javascript
const users = [];
let nextId = 1;

module.exports = {
    findAll: () => {
        return users;
    },
    
    findById: (id) => {
        return users.find(u => u.id === parseInt(id));
    },
    
    findByEmail: (email) => {
        return users.find(u => u.email === email);
    },
    
    create: (data) => {
        const user = {
            id: nextId++,
            ...data,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString()
        };
        users.push(user);
        return user;
    },
    
    update: (id, data) => {
        const index = users.findIndex(u => u.id === parseInt(id));
        if (index === -1) return null;
        
        users[index] = {
            ...users[index],
            ...data,
            updatedAt: new Date().toISOString()
        };
        return users[index];
    },
    
    delete: (id) => {
        const index = users.findIndex(u => u.id === parseInt(id));
        if (index === -1) return false;
        users.splice(index, 1);
        return true;
    }
};

四、验证规则 #

4.1 src/validations/schemas.js #

javascript
const Joi = require('joi');

const userId = Joi.object({
    id: Joi.number().integer().positive().required()
});

const createUser = Joi.object({
    name: Joi.string().min(2).max(50).required(),
    email: Joi.string().email().required(),
    password: Joi.string().min(6).required()
});

const updateUser = Joi.object({
    name: Joi.string().min(2).max(50),
    email: Joi.string().email()
}).min(1);

const pagination = Joi.object({
    page: Joi.number().integer().min(1).default(1),
    limit: Joi.number().integer().min(1).max(100).default(10)
});

module.exports = {
    userId,
    createUser,
    updateUser,
    pagination
};

五、处理函数 #

5.1 src/handlers/users.js #

javascript
const Boom = require('@hapi/boom');
const User = require('../models/user');

const getAll = async (request, h) => {
    const { page, limit } = request.query;
    const users = User.findAll();
    
    const start = (page - 1) * limit;
    const end = start + limit;
    
    return {
        users: users.slice(start, end),
        total: users.length,
        page,
        limit
    };
};

const getById = async (request, h) => {
    const user = User.findById(request.params.id);
    
    if (!user) {
        throw Boom.notFound('User not found');
    }
    
    return user;
};

const create = async (request, h) => {
    const existingUser = User.findByEmail(request.payload.email);
    
    if (existingUser) {
        throw Boom.conflict('Email already exists');
    }
    
    const user = User.create(request.payload);
    return h.response(user).code(201);
};

const update = async (request, h) => {
    const user = User.update(request.params.id, request.payload);
    
    if (!user) {
        throw Boom.notFound('User not found');
    }
    
    return user;
};

const remove = async (request, h) => {
    const deleted = User.delete(request.params.id);
    
    if (!deleted) {
        throw Boom.notFound('User not found');
    }
    
    return h.response().code(204);
};

module.exports = {
    getAll,
    getById,
    create,
    update,
    remove
};

六、路由定义 #

6.1 src/routes/users.js #

javascript
const handlers = require('../handlers/users');
const schemas = require('../validations/schemas');

module.exports = [
    {
        method: 'GET',
        path: '/users',
        options: {
            description: 'Get all users',
            tags: ['api'],
            validate: {
                query: schemas.pagination
            }
        },
        handler: handlers.getAll
    },
    {
        method: 'GET',
        path: '/users/{id}',
        options: {
            description: 'Get user by ID',
            tags: ['api'],
            validate: {
                params: schemas.userId
            }
        },
        handler: handlers.getById
    },
    {
        method: 'POST',
        path: '/users',
        options: {
            description: 'Create new user',
            tags: ['api'],
            validate: {
                payload: schemas.createUser
            }
        },
        handler: handlers.create
    },
    {
        method: 'PUT',
        path: '/users/{id}',
        options: {
            description: 'Update user',
            tags: ['api'],
            validate: {
                params: schemas.userId,
                payload: schemas.updateUser
            }
        },
        handler: handlers.update
    },
    {
        method: 'DELETE',
        path: '/users/{id}',
        options: {
            description: 'Delete user',
            tags: ['api'],
            validate: {
                params: schemas.userId
            }
        },
        handler: handlers.remove
    }
];

6.2 src/routes/index.js #

javascript
const userRoutes = require('./users');

module.exports = [
    {
        method: 'GET',
        path: '/',
        handler: (request, h) => {
            return { message: 'Welcome to API', version: '1.0.0' };
        }
    },
    {
        method: 'GET',
        path: '/health',
        handler: (request, h) => {
            return { status: 'ok', timestamp: new Date().toISOString() };
        }
    },
    ...userRoutes
];

七、错误处理 #

7.1 src/plugins/errorHandler.js #

javascript
const errorHandler = {
    name: 'error-handler',
    register: async (server, options) => {
        server.ext('onPreResponse', (request, h) => {
            const response = request.response;
            
            if (response.isBoom) {
                const error = response;
                
                return h.response({
                    success: false,
                    error: {
                        code: error.output.statusCode,
                        message: error.message,
                        details: error.data
                    }
                }).code(error.output.statusCode);
            }
            
            return h.continue;
        });
    }
};

module.exports = errorHandler;

7.2 src/plugins/index.js #

javascript
const errorHandler = require('./errorHandler');

module.exports = [
    errorHandler
];

八、运行项目 #

8.1 package.json #

json
{
    "name": "hapi-rest-api",
    "version": "1.0.0",
    "scripts": {
        "start": "node src/index.js",
        "dev": "nodemon src/index.js"
    }
}

8.2 启动服务 #

bash
npm run dev

九、API测试 #

9.1 获取所有用户 #

bash
curl http://localhost:3000/users

9.2 创建用户 #

bash
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name":"John Doe","email":"john@example.com","password":"password123"}'

9.3 获取单个用户 #

bash
curl http://localhost:3000/users/1

9.4 更新用户 #

bash
curl -X PUT http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Jane Doe"}'

9.5 删除用户 #

bash
curl -X DELETE http://localhost:3000/users/1

十、总结 #

RESTful API要点:

功能 HTTP方法 路径
获取列表 GET /users
获取单个 GET /users/:id
创建 POST /users
更新 PUT /users/:id
删除 DELETE /users/:id

下一步,让我们学习用户认证系统!

最后更新:2026-03-28