从零搭建 Node.js企业级 Web 服务器:自定义服务&数据请求

自定义服务

Nextjs提供了一个高级特性,它为我们提供了自定义服务器的能力,这使得我们可以在需要时创建自定义路由和处理请求。下面是一个简单的 Next.j 自定义服务器示例,演示了如何创建一个自定义路由:

当你需要在 Next.js 中创建自定义服务器并添加自定义路由时,以下是一些详细步骤:

  1. 创建 Next.js 应用

    首先,确保你已经创建了一个 Next.js 应用。你可以使用以下命令创建一个新的 Next.js 应用:

    bash 复制代码
    $ npx create-next-app my-custom-app
    $ cd my-custom-app
    $ yarn

    这将创建一个基本的Next.js 应用,并安装所需的依赖项。

  2. 创建自定义服务器文件

    Next.js 应用的根目录下,创建一个名为 server.js的文件,用于定义自定义服务器的行为。

  3. 导入依赖项

    server.js 文件中,首先导入必要的依赖项,包括 expressnext

    javascript 复制代码
    const express = require('express');
    const next = require('next');
  4. 配置 Next.js 应用

    使用 next 创建 Next.js 应用的实例,并根据开发或生产环境进行配置:

    javascript 复制代码
    const dev = process.env.NODE_ENV !== 'production';
    const app = next({ dev });
    const handle = app.getRequestHandler();

    这些配置选项将告诉 Next.js 应用是否运行在开发模式下,以及如何处理请求。

  5. 准备应用程序

    使用 app.prepare() 方法准备 Next.js 应用程序以响应请求:

    javascript 复制代码
    app.prepare().then(() => {
      // 在这里定义服务器路由
    });

    这将确保 Next.js 应用程序准备好处理请求。

  6. 定义自定义路由

    server.js 文件中,可以使用 Express.js 来定义自定义路由。例如,下面的代码定义了一个 /custom-route 路由,它返回一个简单的文本响应:

    javascript 复制代码
    const server = express();
    server.get('/custom-route', (req, res) => {
      // 在这里处理自定义路由的请求
      res.send('This is a custom route');
    });

    你可以根据需要定义多个自定义路由。

  7. 默认路由处理

    在自定义路由之后,需要定义一个默认路由,以便处理所有其他请求。通常,这个默认路由会调用 Next.jsgetRequestHandler() 方法:

    javascript 复制代码
    server.get('*', (req, res) => {
      return handle(req, res);
    });

    这确保了 Next.js 应用程序可以处理剩余的路由。

  8. 启动服务器

    最后,使用 server.listen() 启动服务器,并指定要监听的端口:

    javascript 复制代码
    server.listen(3000, (err) => {
      if (err) throw err;
      console.log('> Ready on http://localhost:3000');
    });

    这将启动自定义服务器,并在端口 3000 上监听请求。

  9. 运行自定义服务器

    现在,你可以在终端中运行自定义服务器:

    bash 复制代码
    $ npm start

    访问 http://localhost:3000/custom-route 将会看到 This is a custom route的响应。

这就是如何在 Next.js 中创建一个自定义服务器和自定义路由的详细步骤。通过这种方式,你可以为你的 Next.js 应用程序添加自定义路由和其他自定义功能,以满足特定的需求。请记住,在大多数情况下,使用内置的 Next.js 路由机制足以满足需求,只有在需要更高度定制的情况下才需要自定义服务器。

实战演练

接下来我们结合axios来实现一个简单的项目实战。我们在上一节的基础上进行改造。 打开终端执行命令

bash 复制代码
#新建server目录,并新增app文件
$ mkdir server
$ cd server
$ touch app.ts

#新建utils目录,并新增index文件和axios文件
$ cd ..
$ mkdir utils
$ cd utils
$ touch index.ts
$ touch axios.ts

#新建services目录,并新增index文件和type文件
$ cd ..
$ mkdir services
$ cd services
$ touch index.ts
$ touch type.ts

#新建一个nodemon的配置文件
$ cd ..
$ mkdir nodemon.json

执行完命令之后回到根目录下执行项目结构是这样的:

bash 复制代码
$ tree -I 'node_modules|.next'

├── Dockerfile
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
│   ├── _app.tsx
│   ├── _document.tsx
│   ├── api
│   │   └── hello.ts
│   └── index.tsx
├── public
│   ├── favicon.ico
│   ├── next.svg
│   └── vercel.svg
├── server
│   └── app.ts
├── services
│   └──index.ts
│   └──type.ts
├── styles
│   ├── Home.module.css
│   └── globals.css
├── tsconfig.json
├── utils
│   ├── axios.ts
│   └── index.ts
└── yarn.lock

app.ts文件使用express自定义服务

ts 复制代码
import express, { Request, Response, NextFunction } from 'express';
import next from 'next';
import portfinder from 'portfinder';
import chalk from 'chalk';

const basePort = 3000;
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : basePort; // e.g: PORT=3000 yarn dev
portfinder.basePort = port;

const dev = process.env.NODE_ENV !== 'production';

portfinder.getPort(function (error: Error, nextPort: number) {
  if (error) {
    console.error(error);
    return;
  }
  if (port !== nextPort) {
    process.env.PORT = nextPort + '';
    console.info(
      `${chalk.yellow(
        'warn',
      )}  - Port ${port} is in use, trying ${nextPort} instead.`,
    );
  }

  const app = next({ dev, port: nextPort });
  const handle = app.getRequestHandler();
  app
    .prepare()
    .then(async () => {
      const server = express();

      process.env.PORT = nextPort.toString();
      server.all('*', (req: Request, res: Response, next: NextFunction) => {
        if (/^\/api\/.*/.test(req.url)) {
          res.json({msg:'Hello Warld'})
        } else {
          return handle(req, res);
        }
      });

      server.listen(nextPort, () =>
        console.info(
          `${chalk.green(
            'ready',
          )} - started server on 0.0.0.0:${nextPort}, url: http://localhost:${nextPort}`,
        ),
      );
    })
    .catch((error: Error) => {
      console.error(error);
    });
});

我们使用express进行了自定义服务,并对以/api开头的请求进行了自定义处理,其他路径的请求交于next进行处理。 按照依赖包

bash 复制代码
$ yarn add express portfinder chalk nodemon
$ yarn add ts-node

新增nodemon配置文件

json 复制代码
{
    "watch": ["server/**/*"],
    "ext": "ts",
    "exec": "ts-node server/app.ts"
}

执行脚本 yarn dev

bash 复制代码
$ yarn dev

使用postman进行验证一下

好了,到此为止自定义服务基本完成了。

接下来我们自定义一个axios请求,主要目的是为了简化异步请求逻辑。

在新增的utils/axios文件下编写以下代码。

ts 复制代码
import axioslib, { Method, AxiosHeaders } from 'axios';

export interface BaseResult<T> {
  success: boolean;
  t: number;
  code?: number;
  result: T;
  msg?: string;
}
export const DEVBASEURL = 'http://localhost:3000'; //默认开发环境的baseURL
const DEFAULT_AXIOS_TIMEOUT = 15000; //超时时间
export const axios = axioslib.create({
  timeout: DEFAULT_AXIOS_TIMEOUT,
  baseURL: DEVBASEURL
});

// 生成FormData
function createFormData<F>(forData: F): FormData | null {
  if (!forData) {
    return null;
  }
  const data = new FormData();
  Object.entries(forData).forEach(([name, value]) => {
    data.append(name, value as string);
  });
  return data;
}

// 处理url中的path参数
function pathReplace<P>(url: string, path: P): string {
  if (!path) {
    return url;
  }
  let nextApi = url;
  Object.entries(path).forEach(([name, value]) => {
    const encodedValue = encodeURIComponent(value + '');
    nextApi = nextApi
      .replace(`{${name}}`, encodedValue)
      .replace(`:${name}`, encodedValue);
  });
  return nextApi;
}
// P代表path参数,Q代表query参数,B代表body参数,H代表headers参数,F代表formData参数,R代表返回值
export default async function ajax<
  P,
  Q,
  B,
  H extends AxiosHeaders | null,
  F,
  R,
>(
  method: Method,
  url: string,
  path: P,
  query: Q,
  body: B,
  headers: H,
  formData: F,
): Promise<R> {
  const response = (
    await axios({
      method,
      url: `/api${pathReplace(url, path)}`,
      params: query,
      data: body || createFormData(formData),
      headers: headers || undefined,
    })
  ).data;
  if (!response.success) {
    throw new Error(response.msg || '请求失败');
  }
  return response;
}

添加axios

bash 复制代码
$ yarn add axios

index文件中导出axios实例

js 复制代码
// utils/axios
export { default as axios } from './axios';

最基本的axios我们已经处理完毕,现在让我们来添加一个api接口

打开services文件夹的index.ts文件开始编写我们第一个接口

ts 复制代码
// services/index.ts
import ajax, { BaseResult } from '../utils/axios';
import * as Type from './type';

class Services {
  public static getUserInfo(
    query: Type.GetUserInfo$Query,
  ): Promise<BaseResult<Type.GetUserInfo$Response>> {
    return ajax('GET', '/user/info', null, query, null, null, null);
  }
}

export default Services;

继续type.ts文件定义给个参数的ts类型

ts 复制代码
export interface GetUserInfo$Query {
  id: string;
}

export interface GetUserInfo$Response {
  data: {
    name: string;
    age: number;
  };
}

客户端的工作我们已经处理完毕了,加下来我们要去编写接口逻辑了,切换到server目录下的app.ts。我们编写我们的第一个后台接口。

ts 复制代码
//server/app.ts
import express, { Request, Response } from 'express';
import next from 'next';
import portfinder from 'portfinder';
import chalk from 'chalk';
+ const { parse } = require('url');
const basePort = 3000;
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : basePort; // e.g: PORT=3000 yarn dev
portfinder.basePort = port;

const dev = process.env.NODE_ENV !== 'production';

portfinder.getPort(function (error: Error, nextPort: number) {
  if (error) {
    console.error(error);
    return;
  }
  if (port !== nextPort) {
    process.env.PORT = nextPort + '';
    console.info(
      `${chalk.yellow(
        'warn',
      )}  - Port ${port} is in use, trying ${nextPort} instead.`,
    );
  }

  const app = next({ dev, port: nextPort });
  const handle = app.getRequestHandler();
  app
    .prepare()
    .then(async () => {
      const server = express();

      process.env.PORT = nextPort.toString();
      server.all('*', (req: Request, res: Response) => {
        if (/^\/api\/.*/.test(req.url)) {
          // 拦截以 /api/ 开头的请求,做自定义处理
_          res.json({ msg: 'Hello World!' });
+          const parsedUrl = parse(req.url, true);
+          const { pathname, query } = parsedUrl;
+          if (pathname === '/api/user/info') {
+            if (query?.id === '1') {
+              res.json({ name: '张三', age: 18, success: true });
+            } else {
+              res.json({ msg: '没有找到', success: false });
+            }
+          } else {
+            res.json({ msg: 'Hello World!' });
+          }
        } else {
          // 对其他请求直接返回页面内容
          return handle(req, res);
        }
      });

      server.listen(nextPort, () =>
        console.info(
          `${chalk.green(
            'ready',
          )} - started server on 0.0.0.0:${nextPort}, url: http://localhost:${nextPort}`,
        ),
      );
    })
    .catch((error: Error) => {
      console.error(error);
    });
});

按照处理路径的parse

bash 复制代码
$ yarn add parse

接口已经编写完毕,我们来调用一下这个接口。切换到page/index.tsx,删除多余代码后

ts 复制代码
import Services from 'services';
export default function Home() {
  return (
    <>
      <button
        onClick={async () => {
          await Services.getUserInfo({ id: '1' });
        }}
      >
        按钮
      </button>
    </>
  );
}

执行命令yarn dev,打开http://localhost:3000,点击按钮,打开控制台看看

好的,到此为止你已经基本使用next自定义服务开发接口了。 下一节我们正式踏入web服务构建。

相关推荐
Dcc8 小时前
@tanstack/react-query详解 🔥🔥🔥React的异步数据管理神器
前端·react.js
尘埃不入你眼眸8 小时前
powerShell无法执行npm问题
前端·npm·node.js
itslife8 小时前
vite 源码 -
前端·javascript
我有一棵树8 小时前
npm uninstall 执行的操作、有时不会删除 node_modules 下对应的文件夹
前端·npm·node.js
叫我詹躲躲8 小时前
Linux 服务器磁盘满了?教你快速找到大文件,安全删掉不踩坑!
linux·前端·curl
Mintopia8 小时前
动态数据驱动的 AIGC 模型:Web 端实时更新训练的技术可行性
前端·javascript·aigc
志摩凛8 小时前
前端必备技能:使用 appearance: none 实现完美自定义表单控件
前端·css
枕梦1268 小时前
Elpis:企业级配置化框架的设计与实践
前端
温宇飞8 小时前
HTML 节点绘制顺序详解:深入理解 Stacking Context
前端