在之前的两篇文章里,我们从零搭建了 React Web 项目,Part 3 的任务是创建服务端项目。
项目初始化与 Webpack 配置
在 apps 目录下新建 server 文件夹,在 server 目录下执行 pnpm init
初始化。
安装依赖
# @babel 依赖
pnpm add -D @babel/cli @babel/core @babel/preset-env @babel/preset-typescript --filter server
# typescript 以及 Webpack 插件
pnpm add -D fork-ts-checker-webpack-plugin tsconfig-paths-webpack-plugin typescript --filter server
# Webpack
pnpm add -D webpack webpack-bundle-analyzer webpack-cli webpack-dev-server webpack-merge --filter server
注意区分 --filter server
依赖需要安装到 server
子项目。
安装 koa
pnpm add koa --filter server
pnpm add @types/express -D --filter server
接下来创建一个简单的服务,src/app.ts
import Koa, { Context, Next } from 'koa';
const app = new Koa();
const { PORT = 3000 } = process.env;
app.use(async (ctx: Context, _next: Next) => {
ctx.body = 'Hello world';
});
app.listen(PORT, () => {
console.log('Server running at http://localhost:', PORT);
});
添加 Webpack 配置
实际代码库中没有添加 Webpack 配置,由于 server 代码,只有 Typescript,还是决定开发环境下使用 ts-node 直接运行,实际发布使用
tsc
编译
在 server 目录下新建 webpack 文件夹,存放 server 项目的 Webpack 配置。公用的 Webpack 之前是为 web 项目配置,其中包含例如 html-webpack-plugin,mini-css-extract-plugin 等 server 项目不需要的配置,需要精简 Webpack 并能够同时支持 web 和 server 项目。
之前有过度设计的嫌疑了,Webpack 在 server 项目与 web 项目上的配置有较大差异,目前整体项目还没必要抽离公共的 Webpack 配置,直接回归到为子项目单独配置 Webpack
在 server/webpack 目录下添加 webpack.base.js
配置文件,并添加如下配置。
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
name: 'server',
entry: {
server: path.join(process.cwd(), './src/app.ts'),
},
output: {
path: path.join(process.cwd(), './dist'),
filename: '[name].js',
clean: true,
},
plugins: [new ForkTsCheckerWebpackPlugin()],
resolve: {
extensions: ['.ts', '.js'],
plugins: [new TsconfigPathsPlugin()],
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
loader: require.resolve('babel-loader'),
options: {
cacheDirectory: true,
rootMode: 'upward',
},
},
],
},
};
添加 webpack.config.dev.js
文件,使用 webpack-merge 工具,整合 webpack.base.js
配置
// webpack.config.dev.js
const { merge } = require('webpack-merge');
const base = require('./webpack.base');
module.exports = merge(base, {
mode: 'development',
watch: true,
resolve: {
alias: {},
},
});
在 server/package.json 中添加如下的脚本
{
"scripts": {
"start": "NODE_ENV=development webpack --config webpack/webpack.config.dev.js"
}
}
和 web 项目一样,在根目录下的 package.json 中新增启动服务端项目的脚本
{
"scripts": {
"start:server": "pnpm --filter server start"
}
}
执行 pnpm start:server
打印的日志报错,这是因为 Webpack 的 target
配置的默认值,当项目中存在 browserslist 配置时值为 browserslist
,否则为 web
。针对服务端项目,我们需要设置 target
为 node
,重新执行命令,错误消失了。不过仍然还有一条如下的警告
Critical dependency: the request of a dependency is an expression
查阅资料之后发现,设置了 target:node
,意味着 Webpack 需要打包核心模块,但不打包 node_modules 中的模块。实际上,光靠这一个配置项是不够的,需要借助 webpack-node-externals 帮助我们在打包时忽略 node_modules。
Webpack 打包结束之后,我们可以通过在中断执行 node dist/server.js
来启动项目,使用 nodemon 能够实现监听打包后文件的变化,自动重启 API 服务。
pnpm add -D nodemon nodemon-webpack-plugin --filter server
执行 nodemon ./dist/server.js
前必须确保 Webpack 编译结束,通过 nodemon-webpack-plugin 插件可以帮助我们监听 Webpack 编译完成之后,自动启动 nodemon 服务。执行 pnpm start:server
,之后访问 http://localhost:3000 就能看到返回的 Hello world。
简化! 使用 tsc
server 项目为纯 Typescript 代码,使用 Webpack 总觉得有点杀鸡用牛刀的感觉,上文提到的 Webpack 配置,暂且就当学习记录一下。这一部分简单记录一下开发环境下使用 ts-node
的配置。
安装 ts-node 等依赖
pnpm add -D ts-node @types/node --filter server
修改运行 npm scripts
{
"scripts": {
"start": "nodemon --ignore tests/ --watch src -e ts --exec \"node --inspect --require ts-node/register src/app.ts\""
}
}
使用
--inspect
开启 nodejs 的调试模式
添加路由
相比于 Express 的大而全,Koa 更轻量,例如 Koa 并不提供开箱即用的路由配置选项。如果需要添加路由管理,需要借助 @koa/router。
安装 @koa/router
pnpm add @koa/router --filter server
pnpm add @types/koa__router -D --filter server
规定服务端项目文件结构
在搜索了多个 nodejs 服务端示例项目后,总结常见的路由与业务逻辑分离的文件结构,大致如下
src/
controllers/ # 业务逻辑
routes/ # 定义路由
index.ts
app.ts # 项目 entry
接下来在 routes/index.ts 下添加如下代码
import { Context } from 'koa';
import Router from '@koa/router';
// 统一添加路由 /api 前缀
const router = new Router({
prefix: '/api',
});
router.get('/', (ctx: Context) => {
ctx.body = 'Hello world';
});
export default router;
修改 src/app.ts 文件内容,导入 routes/index.ts 文件中定义的路由
import Koa from 'koa';
import router from './routes';
const app = new Koa();
const { PORT = 3000 } = process.env;
app
.use(router.routes())
.use(router.allowedMethods())
.listen(PORT, () => {
console.log('Server running at http://localhost:', PORT);
});
重启服务,在浏览器中打开 http://localhost:3000/api 就可以看到打印的 Hello world
了。
未完成的工作
到这里创建 server 项目就告一段落了,当然一个完整的服务端项目还有许多工作许多配置需要完成。
- 使用 Decorators 装饰器 简化
route
的定义 - prisma 与数据库配置
- 编写测试 API 并在
web
项目中实际调用 - API 添加单元测试