mkdir koa-demo
yarn init
yarn add koa koa-router
这里使用 yarn
包管理器进行项目依赖管理。
// index.js
const app = require('./app');
const port = 4000;
app.listen(port, () => {
console.log(`Koa项目启动成功:http://localhost:${port}`);
});
// app.js
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.get('/', async (ctx, next) => {
ctx.body = '这是主页
';
});
router.get('/hello/:name', async (ctx, next) => {
ctx.body = `你好,
${ctx.params.name}`;
});
app.use(router.routes());
module.exports = app;
// package.json
{
"name": "koa-demo",
"version": "1.0.0",
"description": "koa项目基本目录结构",
"main": "index.js",
"repository": "https://gitee.com/ljw_full/koa-demo.git",
"author": "ljw",
"license": "MIT",
"scripts": {
"dev": "node index.js"
},
"dependencies": {
"koa": "^2.11.0",
"koa-router": "^8.0.8"
}
}
这就是一个最简单的Koa项目了,使用yarn dev
后就会在4000端口启动一个WEB服务:
在开发时,经常需要改代码看效果,但是每次手动关闭项目,启动项目很麻烦,所以就用到 nodemon
这个自动重启工具,每次修改完代码保存后,就可以自动重启整个项目查看效果了。
npm i nodemon -g
这里我使用全局安装 nodmeon
,因为我只配置了 npm
的path,所以这里就用 npm
安装 nodemon
。
修改 package.json
的 script.dev
脚本为:nodemon index.js
在这里你使用 import
会发现报错 SyntaxError: Unexpected identifier
(Node支持大部分ES6语法,不过这个import
关键字却没有)
这里需要使用 babel
来进行编译才可以执行:
yarn add babel-cli babel-preset-es2015 -D
然后再根目录添加一个新文件 .babelrc
:
{
"presets": ["es2015"]
}
启动脚本配置如下:
{
"scripts": {
"dev": "nodemon index.js --exec babel-node",
"prod": "babel-node index.js"
},
}
一个项目的开发都有一套良好的目录结构,什么代码放哪个目录下。
.
├── app.js
├── index.js # 项目启动入口
├── package.json
├── src
├── main # 项目代码
│ ├── common # 通用模块
│ ├── config # 配置
│ ├── controller # 接口
│ ├── middleware # 中间件
│ ├── model # 模型层
│ └── service # 服务层
│ └── util # 工具层
└── test # 测试目录
这里的 app.js 和 index.js 分开来写是为了方便测试接口
通常一些配置都会分为开发环境,测试环境,生成环境,最典型的就是数据库连接信息了。
这里使用 dotenv
来配置 env:yarn add docenv
然后创建一个 .env
文件:
NODE_ENV=dev
PORT=4000
在 index.js
中最上方添加一行代码:
import 'dotenv/config';
import Koa from 'koa';
import router from './src/main/router';
const app = new Koa();
// 加载所有路由
router(app);
export default app;
一般 .env
文件不会进入git版本库,我通常都会放一个 .env.example
里面写着样例配置,然后根据这个 .env.example
复制出 .env
文件编写真正的信息。
你的一些私密信息都可以写在 .env
文件里,例如你的数据库连接信息,一些第三方的私钥密钥信息等。
开发一个项目,通常都会有很多的接口,所以不可能把接口都写在app.js
里面,我这里把接口都放到了src/main/controller
目录下,例如现在我编写了一个测试模块:
// src/main/api/TestController.js
import Router from 'koa-router';
let testRouter = new Router({
prefix: '/test'
});
testRouter.get('/', async ctx => {
ctx.body = '测试主页
';
});
testRouter.get('/hello/:name', async ctx => {
ctx.body = `你好啊,
${ctx.params.name}`;
});
export default testRouter;
这里使用一个require-directory
库,他可以很方便的递归检索目录下的文件:yarn add require-directory
// src/main/router.js
import requireDirectory from 'require-directory';
import Router from 'koa-router';
export default app => {
requireDirectory(module, './controller', {
visit: (obj) => {
if (obj.default instanceof Router) {
app.use(obj.default.routes());
}
}
})
}
这里遍历递归 src/main/controller
目录,visit
接受一个函数,obj 就是那个文件export的对象,如果这个 obj 是 Router
实例,就加载到路由,这样子,你在 src/main/controller
下编写多少个文件都可以自动加载。
最后在 app.js
中引入这个 router.js
文件:
import Koa from 'koa';
import router from './src/main/router';
const app = new Koa();
// 加载所有路由
router(app);
export default app;
export const COMMON_ERROR_CODE = 500;
export const PARAM_ERROR_CODE = 501;
/**
* 通用异常
*/
export class CommonError extends Error {
constructor(code = COMMON_ERROR_CODE, msg = '服务器异常') {
super();
this.code = code;
this.msg = msg;
}
}
/**
* 参数异常
*/
export class ParamError extends CommonError {
constructor(msg = '参数异常') {
super(PARAM_ERROR_CODE, msg);
}
}
// src/main/middleware/globalError.js
import { CommonError } from '../common/CommonError';
export default () => {
return async (ctx, next) => {
try {
await next();
} catch (err) {
if (err instanceof CommonError) {
ctx.body = err;
} else {
ctx.body = new CommonError();
}
}
};
};
不过这里的 err instanceof CommonError
可能一直都是 false
,需要添加一个 babel
插件:yarn add babel-plugin-transform-builtin-extend -D
在 .babelrc
中配置一下插件:
{
"presets": ["es2015"],
"plugins": [
[
"babel-plugin-transform-builtin-extend",
{ "globals": ["Error", "Array"] }
]
]
}
最后在 app.js
中引入中间件:
import Koa from 'koa';
import router from './src/main/router';
import globalError from './src/main/middleware/globalError';
const app = new Koa();
// 全局异常处理
app.use(globalError());
// 加载所有路由
router(app);
export default app;
使用:
import Router from 'koa-router';
import { ParamError, CommonError } from '../common/CommonError';
let testRouter = new Router({
prefix: '/test'
});
testRouter.get('/exception', async ctx => {
// ctx.body = ctx._matchedRouteName.msg;
throw new ParamError('缺少参数');
});
export default testRouter;
中间件会统一放到 src/main/middleware
目录下,如果中间件过多在 app.js
中引入也是比较麻烦,所以这里可以写个中间件配置。
配置文件在 src/main/config
目录下:
// src/main/config/index.js
export default {
middlewares: [
'globalError'
]
};
这里的 middlewares
是个数组,存放的是 middleware
目录下的中间件的文件名,带不带 .js
都可以。
然后在 middleware.js
中处理中间件:
// src/main/middleware.js
import config from './config';
export default (app) => {
if (config.middlewares) {
let middleware;
config.middlewares.forEach(item => {
middleware = require(`./middleware/${item}`).default;
app.use(middleware());
});
}
};
在app.js
中处理 middleware.js
:
import Koa from 'koa';
import router from './src/main/router';
import middleware from './src/main/middleware';
const app = new Koa();
// 中间件处理
middleware(app);
// 加载所有路由
router(app);
export default app;
添加一个中间件,在 config/index.js
的 middlewares
数组中添加一下中间件文件名即可。
如果的你的目录层次够深,可能会遇到 import '../../../src/main/config';
的代码,此时我们可以使用 @main
来代替 src/main
目录。
安装:yarn add module-alias
在 package.json
中添加:
{
"_moduleAliases": {
"@main": "./src/main"
}
}
在 app.js
中引入一个依赖:
import 'module-alias/register';
import Koa from 'koa';
import router from '@main/router.js';
import middleware from '@main/middleware';
const app = new Koa();
// 中间件处理
middleware(app);
// 加载所有路由
router(app);
export default app;
接下来下面的所有地方都可以使用 @main
这个目录别名了。
但是此时 VScode 的路径提示可能会没有了,需要在 根目录下添加一个 jsconfig.json
文件:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"baseUrl": "./",
"paths": {
"@main/*": ["src/main/*"]
}
},
"exclude": [
"node_modules"
]
}
点击查看源码
相关文章: