Sitemap

使用 NodeJs 製作 API SERVER

11 min readNov 8, 2022

--

快速建立API SERVER,可直接進行實務面開發。

環境設定
node: v16.14.2
npm: v8.5.0

創建專案資料夾(api-server) & 專案初始化

mkdir api-server
cd api-server
npm init

安裝相關套件

npm i express
npm i compression //壓縮回應內文 (express官方推薦)
npm i cors //跨來源資源共享 (express官方推薦)
npm i body-parser //HTTP請求解析 (express官方推薦)
npm i cookie-parser //cookie解析 (express官方推薦)
npm i helmet //設定http標頭 (express官方推薦)
npm i moment //日期時間
npm i moment-timezone //時區
npm i dotenv //環境變數
npm i http-errors //處理HTTP Error
npm i require-dir //增強require應用

參考來源

創建程式資料夾

public 放置前端用靜態資源(.jpg / .png / .css / .js / .html)

mkdir src
mkdir src/public
mkdir src/routes-api
mkdir src/routes-api/api

建立入口點,新增檔案main.js

./src/routes-api/main.js

建立環境變數,新增檔案.env

./.env

修改package.json

//修改前
{
"name": "api-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
...
}
}
//修改後
{
"name": "api-server",
"version": "1.0.0",
"scripts": {
"start": "node ./src/routes-api/main.js"
},
"dependencies": {
...
}
}

修改好後,啟動指令就能用了!

npm start

開始編輯主程式

(./src/routes-api/main.js)

//引入套件
require('dotenv').config();
const compression = require('compression');
const createError = require('http-errors');
const express = require('express');
const cors = require('cors');
const dir = require('require-dir');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const moment = require('moment');
const tz = require('moment-timezone');
const path = require('path');
const http = require('http');
const helmet = require('helmet');
const app = express();
//讀取環境變數
moment.tz.setDefault(process.env['DEFAULT_TIME_ZOME']);
const MAIN_NODE_PORT = process.env['MAIN_NODE_PORT'];
const MAIN_API_TIMEOUT = parseInt(process.env['MAIN_API_TIMEOUT']);
let server = null;
//各套件初始化
app.use(cors());
app.use(helmet({
frameguard: true,
xssFilter: true,
noSniff: true
}));
app.use(bodyParser.json({
limit: '10mb',
verify: function (req, res, buf, encoding) {
req.rawBody = buf;
},
}));
app.use(bodyParser.urlencoded({ extended: false, }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '../public'))); //指定靜態目錄裝載路徑
app.use(compression());
//========== 實作API開始 ====================function access_log(req, res, next) {
const dd = Date.now();
const url = req.path;
const remoteIP = (req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.ip || '0.0.0.0');
console.debug(`req from ${req.method.toUpperCase()} ${url} ${JSON.stringify(req.body)}, remoteIP:${remoteIP}`);
res.once('finish', () => {
console.debug(`response from ${req.method.toUpperCase()} ${url} ${JSON.stringify(req.body)} ${res.statusCode}, cost:${Date.now() - dd}`);
})
next();
}
//呼叫API, 直接返回結果
Object.keys(dir(`./api`)).forEach((controller) => {
require(`./api/${controller}`)(app, [access_log]);
});
//查無API, 返回404
app.use(function (req, res, next) {
next(createError(404));
});
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.json({ code: err.status, message: res.locals.message });
});
//========== 實作API結束 ====================//程式進入點
(async function start() {
try {
//連線db
//....
//啟動express
server = http.createServer(app);
server.on('error', onServerErrorHandle);
server.setTimeout(MAIN_API_TIMEOUT);
server.listen(MAIN_NODE_PORT);
console.log('api server pid:', process.pid, 'listen:', MAIN_NODE_PORT);
} catch (err) {
console.error(err);
}
})();
//啟動express錯誤,事件處理函式
function onServerErrorHandle(error) {
try {
if (error.syscall !== 'listen') throw error;
let bind = typeof MAIN_NODE_PORT === 'string' ? 'Pipe ' + MAIN_NODE_PORT : 'Port ' + MAIN_NODE_PORT;
switch (error.code) {
case 'EACCES':
console.log(bind + ' requires elevated privileges (use sudo)');
process.exit(1);
break;
case 'EADDRINUSE':
console.log(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
} catch (err) {
console.error(err);
}
}
//偵聽nodejs, 進程終止信號
process.once('SIGINT', () => {
server && server.close(() => {
console.log('process terminated');
process.exit(0);
})
});

編輯環境變數

Get Ailsa Xu’s stories in your inbox

Join Medium for free to get updates from this writer.

(./.env)

MAIN_NODE_PORT=8081 #啟動後的API Server PORT
MAIN_API_TIMEOUT=60000
DEFAULT_TIME_ZOME=Etc/UTC #啟動後的API Server時區

主程式完成!

接下來~

撰寫API,在指定目錄下,新增JS檔案!

(一個API,一份JS檔案)

GET API 範本

./src/routes-api/api/01–01_get-helloworld.js

const express = require('express');module.exports = function (app, common_middleware = []) {
const router = express.Router();
app.use(router);
/**
* GET API
*/
router.get(['/test/get-helloworld'], [...common_middleware], async (req, res, next) => {
try {
const { name } = req.query; //req.query
return res.json({ code: 0, data: name });
} catch (err) {
console.error(err);
return res.status(500).json(
{ code: 500, message: `something wrong` }
);
}
});
}

POST API 範本

./src/routes-api/api/01–02_post-helloworld.js

const express = require('express');module.exports = function (app, common_middleware = []) {
const router = express.Router();
app.use(router);
/**
* POST API
*/
router.post(['/test/post-helloworld'], [...common_middleware], async (req, res, next) => {
try {
const { name } = req.body; //req.body
return res.json({ code: 0, data: name });
} catch (err) {
console.error(err);
return res.status(500).json(
{ code: 500, message: `something wrong` }
);
}
});
}

使用 POSTMAN 進行 API 測試

Press enter or click to view image in full size
Press enter or click to view image in full size

** 完成 **

最後的,專案資料夾結構~

專案下載Github位置

使用方式

npm i
npm start

--

--