MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Next.js API Routes 入门指南:快速构建后端服务

2022-07-101.5k 阅读

Next.js API Routes 基础概念

Next.js 是一个基于 React 的轻量级前端框架,它在服务端渲染(SSR)、静态站点生成(SSG)等方面表现出色。而 Next.js API Routes 则是 Next.js 提供的一项强大功能,允许开发者在 Next.js 项目中轻松构建后端 API 服务。这意味着你无需额外设置一个独立的后端服务器项目,就可以在同一个代码库中处理前端与后端逻辑,极大地提高了开发效率。

从本质上来说,Next.js API Routes 利用了 Node.js 的能力。当你在 Next.js 项目的 pages/api 目录下创建文件时,Next.js 会自动将其识别为 API 路由。这些文件遵循特定的请求处理模式,类似于传统的 Node.js 后端路由处理,例如 Express.js 等框架中的路由。

创建第一个 Next.js API Route

  1. 项目初始化 首先,确保你已经安装了 Node.js 和 npm(Node Package Manager)。然后,使用以下命令创建一个新的 Next.js 项目:
npx create-next-app my - next - app
cd my - next - app

这将创建一个名为 my - next - app 的新 Next.js 项目,并进入该项目目录。 2. 创建 API Route 文件pages/api 目录下创建一个新文件,例如 hello.js。在 Next.js 中,文件名会映射到 API 路由的路径。所以,pages/api/hello.js 将对应 /api/hello 这个 API 路由。

// pages/api/hello.js
export default function handler(req, res) {
    res.status(200).json({ message: 'Hello, Next.js API Route!' });
}

上述代码定义了一个简单的 API 路由处理函数。req 是一个包含请求信息的对象,类似于 Node.js 中 HTTP 请求对象,res 则是用于响应请求的对象,类似于 HTTP 响应对象。这里,我们只是返回一个简单的 JSON 数据,状态码设置为 200,表示成功。 3. 启动项目并测试 API 在项目根目录下运行以下命令启动 Next.js 开发服务器:

npm run dev

然后,在浏览器中访问 http://localhost:3000/api/hello,你将看到浏览器显示 {"message":"Hello, Next.js API Route!"}。你也可以使用工具如 Postman 来测试这个 API,它能更方便地查看请求和响应的详细信息。

处理不同的 HTTP 方法

一个 API 通常需要处理多种 HTTP 方法,如 GET、POST、PUT、DELETE 等。在 Next.js API Routes 中,处理不同的 HTTP 方法非常直观。

  1. 处理 GET 请求 继续使用上面的 hello.js 文件,默认情况下,它处理的就是 GET 请求。如果我们想要从请求中获取查询参数,可以这样做:
// pages/api/hello.js
export default function handler(req, res) {
    const { name } = req.query;
    const message = name? `Hello, ${name}!` : 'Hello, stranger!';
    res.status(200).json({ message });
}

现在,你可以访问 http://localhost:3000/api/hello?name=John,将得到 {"message":"Hello, John!"}。 2. 处理 POST 请求 假设我们想要创建一个接收用户提交数据的 API,比如一个简单的用户注册 API。在 pages/api/register.js 中编写如下代码:

// pages/api/register.js
export default function handler(req, res) {
    if (req.method === 'POST') {
        const { username, password } = req.body;
        // 这里可以进行数据验证和数据库操作等
        res.status(201).json({ message: 'User registered successfully', username });
    } else {
        res.status(405).json({ message: 'Method Not Allowed' });
    }
}

在这个例子中,我们首先检查请求方法是否为 POST。如果是,我们从 req.body 中获取用户名和密码(假设前端以 JSON 格式发送数据)。这里只是简单地返回注册成功的消息,实际应用中可能会涉及到数据库插入操作等。如果请求方法不是 POST,我们返回 405 Method Not Allowed 状态码。

与数据库交互

  1. 选择数据库和数据库驱动 Next.js API Routes 可以与各种数据库进行交互,如 MySQL、MongoDB、PostgreSQL 等。以 MongoDB 为例,我们需要安装 mongodb 包。在项目根目录下运行:
npm install mongodb
  1. 连接 MongoDB 并进行操作 假设我们要创建一个 API 来获取博客文章列表。在 pages/api/blogs.js 中编写如下代码:
import { MongoClient } from'mongodb';

const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function getBlogs() {
    try {
        await client.connect();
        const db = client.db('myblog');
        const collection = db.collection('blogs');
        const blogs = await collection.find({}).toArray();
        return blogs;
    } finally {
        await client.close();
    }
}

export default async function handler(req, res) {
    if (req.method === 'GET') {
        const blogs = await getBlogs();
        res.status(200).json(blogs);
    } else {
        res.status(405).json({ message: 'Method Not Allowed' });
    }
}

在上述代码中,我们首先创建了一个 MongoClient 实例,并定义了连接字符串。getBlogs 函数负责连接到 MongoDB,选择数据库和集合,并获取所有博客文章。在 API 处理函数中,当接收到 GET 请求时,我们调用 getBlogs 函数获取博客文章列表并返回给客户端。

错误处理

  1. HTTP 状态码错误处理 在前面的例子中,我们已经看到了一些简单的错误处理,比如返回 405 Method Not Allowed 状态码。当处理数据验证错误等情况时,也可以返回合适的状态码。例如,在用户注册 API 中,如果用户名或密码为空:
// pages/api/register.js
export default function handler(req, res) {
    if (req.method === 'POST') {
        const { username, password } = req.body;
        if (!username ||!password) {
            res.status(400).json({ message: 'Username and password are required' });
        } else {
            // 这里可以进行数据验证和数据库操作等
            res.status(201).json({ message: 'User registered successfully', username });
        }
    } else {
        res.status(405).json({ message: 'Method Not Allowed' });
    }
}

这里,如果用户名或密码为空,我们返回 400 Bad Request 状态码,并给出相应的错误消息。 2. 异步操作错误处理 在与数据库等进行异步操作时,也需要处理可能出现的错误。以 MongoDB 操作为例,修改 getBlogs 函数如下:

import { MongoClient } from'mongodb';

const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function getBlogs() {
    try {
        await client.connect();
        const db = client.db('myblog');
        const collection = db.collection('blogs');
        const blogs = await collection.find({}).toArray();
        return blogs;
    } catch (error) {
        console.error('Error fetching blogs:', error);
        throw new Error('Failed to fetch blogs');
    } finally {
        await client.close();
    }
}

export default async function handler(req, res) {
    if (req.method === 'GET') {
        try {
            const blogs = await getBlogs();
            res.status(200).json(blogs);
        } catch (error) {
            res.status(500).json({ message: 'Internal Server Error' });
        }
    } else {
        res.status(405).json({ message: 'Method Not Allowed' });
    }
}

getBlogs 函数中,我们捕获可能出现的错误,并在控制台打印错误信息,同时抛出一个新的错误。在 API 处理函数中,我们捕获 getBlogs 函数抛出的错误,并返回 500 Internal Server Error 状态码给客户端。

中间件的使用

  1. 什么是中间件 中间件是在请求到达最终处理函数之前或之后执行的函数。它可以用于多种目的,如日志记录、身份验证、数据预处理等。在 Next.js API Routes 中,虽然没有像 Express.js 那样完整的中间件系统,但我们可以通过一些方法实现类似的功能。
  2. 创建简单的日志中间件 假设我们要创建一个日志中间件,记录每个 API 请求的信息。首先,在 lib 目录下创建一个 logger.js 文件(你可以根据项目结构调整目录):
// lib/logger.js
export default function logger(req, res, next) {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    next();
}

然后,在 API 处理函数中使用这个中间件。以 hello.js 为例:

// pages/api/hello.js
import logger from '../../lib/logger';

export default function handler(req, res) {
    logger(req, res, () => {
        res.status(200).json({ message: 'Hello, Next.js API Route!' });
    });
}

在上述代码中,我们引入了 logger 中间件,并在 API 处理函数中调用它。logger 函数记录请求的时间、方法和 URL,然后调用 next 函数,将控制权传递给下一个处理逻辑,这里就是返回响应的逻辑。 3. 链式调用多个中间件 我们可以链式调用多个中间件。例如,假设我们还有一个数据验证中间件 validateQuery,用于验证查询参数:

// lib/validateQuery.js
export default function validateQuery(req, res, next) {
    const { name } = req.query;
    if (!name) {
        res.status(400).json({ message: 'Name is required in query' });
    } else {
        next();
    }
}

现在,修改 hello.js 来链式调用这两个中间件:

// pages/api/hello.js
import logger from '../../lib/logger';
import validateQuery from '../../lib/validateQuery';

export default function handler(req, res) {
    logger(req, res, () => {
        validateQuery(req, res, () => {
            const { name } = req.query;
            const message = `Hello, ${name}!`;
            res.status(200).json({ message });
        });
    });
}

这样,当请求到达 hello.js 时,首先会经过 logger 中间件记录日志,然后经过 validateQuery 中间件验证查询参数,只有验证通过才会返回相应的响应。

部署 Next.js API Routes

  1. 构建项目 在部署之前,首先需要构建 Next.js 项目。在项目根目录下运行:
npm run build

这将在 .next 目录下生成生产环境的构建文件。 2. 选择部署平台

  • Vercel:Vercel 是 Next.js 的官方推荐部署平台。如果你有 Vercel 账号,只需将项目代码推送到 GitHub 或 GitLab 等代码托管平台,然后在 Vercel 上导入项目,Vercel 会自动检测项目类型为 Next.js,并进行构建和部署。Vercel 还会自动处理 API Routes,将 pages/api 目录下的文件作为 API 服务部署。
  • 其他平台:你也可以部署到传统的服务器上,如 AWS、Google Cloud、Heroku 等。以 Heroku 为例,需要在项目根目录下创建一个 Procfile 文件,内容如下:
web: node. next/server

然后,将项目代码推送到 Heroku 远程仓库,Heroku 会根据 Procfile 中的配置启动 Next.js 应用,包括 API Routes 服务。

性能优化

  1. 缓存策略 对于一些不经常变化的数据 API,可以设置合适的缓存策略。在 Next.js API Routes 中,可以通过设置响应头来实现。例如,对于博客文章列表 API,假设文章列表更新不频繁:
// pages/api/blogs.js
import { MongoClient } from'mongodb';

const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);

async function getBlogs() {
    try {
        await client.connect();
        const db = client.db('myblog');
        const collection = db.collection('blogs');
        const blogs = await collection.find({}).toArray();
        return blogs;
    } finally {
        await client.close();
    }
}

export default async function handler(req, res) {
    if (req.method === 'GET') {
        const blogs = await getBlogs();
        res.setHeader('Cache - Control','s - maxage=60, stale - while - revalidate');
        res.status(200).json(blogs);
    } else {
        res.status(405).json({ message: 'Method Not Allowed' });
    }
}

上述代码中,Cache - Control 头设置了缓存策略,s - maxage=60 表示在代理服务器(如 CDN)中缓存 60 秒,stale - while - revalidate 表示在缓存过期后,仍可以使用旧的缓存数据,同时后台重新验证和更新缓存。 2. 优化数据库查询 在与数据库交互时,优化查询语句可以显著提高性能。例如,在 MongoDB 中,如果只需要获取博客文章的标题和简介,可以使用投影操作:

async function getBlogs() {
    try {
        await client.connect();
        const db = client.db('myblog');
        const collection = db.collection('blogs');
        const blogs = await collection.find({}, { projection: { title: 1, intro: 1, _id: 0 } }).toArray();
        return blogs;
    } finally {
        await client.close();
    }
}

这里,{ projection: { title: 1, intro: 1, _id: 0 } } 表示只获取 titleintro 字段,并且不返回 _id 字段,减少了数据传输量。

安全性考虑

  1. 防止 SQL 注入(针对关系型数据库) 如果使用关系型数据库,如 MySQL,要防止 SQL 注入攻击。例如,在处理用户登录 API 时,假设使用 mysql 包:
import mysql from'mysql';

const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'users'
});

export default function handler(req, res) {
    if (req.method === 'POST') {
        const { username, password } = req.body;
        const query = 'SELECT * FROM users WHERE username =? AND password =?';
        connection.query(query, [username, password], (error, results, fields) => {
            if (error) {
                res.status(500).json({ message: 'Database error' });
            } else {
                if (results.length > 0) {
                    res.status(200).json({ message: 'Login successful' });
                } else {
                    res.status(401).json({ message: 'Invalid credentials' });
                }
            }
        });
    } else {
        res.status(405).json({ message: 'Method Not Allowed' });
    }
}

在上述代码中,使用 ? 占位符来代替直接拼接用户名和密码到 SQL 语句中,这样可以防止恶意用户通过输入特殊字符来篡改 SQL 语句。 2. 身份验证和授权 对于需要保护的 API,需要进行身份验证和授权。可以使用 JSON Web Tokens(JWT)等技术来实现。例如,在 pages/api/protected.js 中:

import jwt from 'jsonwebtoken';

export default function handler(req, res) {
    const token = req.headers.authorization;
    if (!token) {
        res.status(401).json({ message: 'Token is missing' });
    } else {
        try {
            const decoded = jwt.verify(token.replace('Bearer ', ''), 'your - secret - key');
            // 这里可以进行授权检查,例如检查用户角色等
            res.status(200).json({ message: 'This is a protected API', data: decoded });
        } catch (error) {
            res.status(401).json({ message: 'Invalid token' });
        }
    }
}

在这个例子中,我们从请求头中获取 Authorization 字段,验证 JWT 令牌。如果令牌缺失或无效,返回相应的错误消息。如果验证通过,可以进一步进行授权检查,如检查用户是否有访问该 API 的权限。

与前端交互

  1. 在 Next.js 前端页面中调用 API 在 Next.js 项目的前端页面中,可以使用 fetch 等方法来调用 API Routes。例如,在 pages/index.js 中:
import React, { useEffect, useState } from'react';

export default function Home() {
    const [blogs, setBlogs] = useState([]);

    useEffect(() => {
        const fetchBlogs = async () => {
            const response = await fetch('/api/blogs');
            const data = await response.json();
            setBlogs(data);
        };
        fetchBlogs();
    }, []);

    return (
        <div>
            <h1>Blog List</h1>
            {blogs.map((blog, index) => (
                <div key={index}>
                    <h2>{blog.title}</h2>
                    <p>{blog.intro}</p>
                </div>
            ))}
        </div>
    );
}

在上述代码中,通过 useEffect 钩子在组件挂载时调用 /api/blogs API,获取博客文章列表,并将数据显示在页面上。 2. 处理前端与后端的数据传递和验证 前端在向 API 发送数据时,需要确保数据的格式正确。例如,在用户注册表单提交时,在前端使用 fetch 发送数据:

import React, { useState } from'react';

export default function Register() {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const handleSubmit = async (e) => {
        e.preventDefault();
        const response = await fetch('/api/register', {
            method: 'POST',
            headers: {
                'Content - Type': 'application/json'
            },
            body: JSON.stringify({ username, password })
        });
        const data = await response.json();
        console.log(data);
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>Username:</label>
            <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
            <label>Password:</label>
            <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
            <button type="submit">Register</button>
        </form>
    );
}

在后端 register.js API 中,要对前端发送的数据进行验证,如前面提到的检查用户名和密码是否为空,确保数据的完整性和安全性。

通过以上内容,你已经全面了解了 Next.js API Routes 的使用,从基础概念、创建 API、处理不同 HTTP 方法、与数据库交互,到错误处理、中间件使用、部署、性能优化、安全性考虑以及与前端的交互等方面。希望这些知识能帮助你在 Next.js 项目中快速构建高效、安全的后端服务。