آموزش ایجاد RESTful API با Express و OpenAPI
۳ اردیبهشت ۱۴۰۰
در این مقاله میخواهیم به نحوهی ایجاد RESTful API با Express و OpenAPI بپردازیم اما در ابتدا باید چالشهایی که ایجاد RESTful API برای ما ایجاد میکند را بدانیم تا با استفاده از استانداردهای موجود یک راه حل پیشنهادی برای مهار کردن چالشها پیدا کنیم.
همچنین باید اشاره داشته باشیم که در این مقاله به مسائل مقدماتی پرداخته نمیشود. یعنی فرض بر این است که شما از قبل با فریمورک Express و مقدمات REST API آشنا هستید. البته مطالعهی مقالههای آموزش مقدماتی فریمورک Express و آموزش ساخت RESTful API با Nodejs میتواند در کسب اطلاعات بیشتر به شما کمک کند.
چالشهای کار با RESTful API
با رشد تیم و پیچیدهتر شدن کدهای اصلی برنامه بدون درنظر گرفتن اینکه از کدام stack برای توسعهی برنامههایتان استفاده میکنید، چالشهای زیادی برای روبرو شدن وجود دارد. بنابراین برای اینکه ارتباط محتوا را از دست ندهیم، چالشها را به برنامههای Express.js و RESTFul APIها محدود میکنیم.
صرف نظر از ماهیت پلتفرمی که بعدا میخواهید از این APIها در آن استفاده کنید احتمالا با چالشهای زیر روبرو خواهید شد:
۱) ایجاد تغییرها دشوار است
این احتمال وجود دارد بندهایی در قرارداد شما با سرمایهگذار پروژه وجود داشته باشد که بهصراحت مشخص نباشند یا حتی نیاز به ایجاد تغییر در ساختار برنامه وجود داشته باشد. برای مثال یک REST endpoint را درنظر بگیرید که نام کاربر را return میکند اما نیاز میشود تغییرهایی ایجاد کنیم تا سن کاربر را هم به ما برگرداند. ایجاد این تغییرها ممکن است برنامه را با مشکل روبرو کند.
برای جلوگیری از این مشکلها میتوانید از integration testها کمک بگیرید اما بازهم این رویکرد نمیتواند بهطور کامل از رخ دادن مشکلها جلوگیری کند.
۲) فقدان مستندات بهروز شده
مستندات یکی دیگر از موضوعهای حساس هنگام ساخت REST API است. با درنظر گرفتن پیچیدگیهای برنامه، بررسی امنیت، پارامترها و پاسخهای احتمالی برای هر endpoint مطمئنا سرعت توسعه کاهش پیدا میکند. حال اگر تیم به ایجاد مستندات متعهد شده باشد بایستی در یک سند جداگانه تمام راهنمای استفاده از APIها را بهروز نگه دارد و این کار بسیار دشوار است.
توسعه و پیادهسازی پروژه
در بخش قبل به برخی چالشهایی که بهطور ذاتی با توسعهی RESTful APIها برای ما بهوجود میآیند پرداختیم. حال در این بخش میخواهیم یک برنامهی Todo را با استفاده از فریمورک Express که از استانداردهای OpenAPI پیروی میکند، توسعه دهیم.
یعنی کاربر ما با دسترسی به این برنامه میتواند لیستی از کارها را در برنامه قرار دهد، آنها را اصلاح یا حتی حذف کند بنابراین متدهای HTTP ما برای توسعهی این برنامه بهشکل زیر است:
[GET] /todos
[POST] /todos
[PUT] /todos/:id
[DELETE] /todos/:id
قبل از توسعهی پروژه بهتر است یک دید کلی از ساختار فایلهای پروژه داشته باشید تا در ادامهی مقاله سردرگم نشوید:
│ .gitignore
│ app.js
│ config.json
│ liara.json
│ package-lock.json
│ package.json
│
├───api
│ │ api-doc.js
│ │
│ └───paths
│ └───todos
│ index.js
│
├───bin
│ www
│
├───public
│ │ index.html
│ │
│ ├───images
│ ├───javascripts
│ └───stylesheets
│ style.css
│
└───routes
index.js
users.js
ایجاد یک پروژه Express
همانطور که از عنوان مقاله پیداست میخواهیم از فریمورک Express برای توسعهی APIهای برنامهی Todo استفاده کنیم:
npx express-generator --no-view --git express-open-api
از express-open-api
بهعنوان نام پروژه استفاده میشود و --git
یک فایل .gitignore
برای ما ایجاد میکند.
نصب پیشنیازها
پس از ایجاد پروژه باید با اجرای دستور cd express-open-api
وارد مسیر پروژه شویم و وابستگیهای اولیه پروژه را نصب کنیم:
npm install
پس از نصب وابستگیهای اولیه پروژه، بهسراغ نصب کتابخانه express-openapi
میرویم:
npm i express-openapi
برای نمایش مستندات API از swagger-ui-express
استفاده خواهیم کرد که با اجرای دستور زیر نصب میشود:
npm i swagger-ui-express
داینامیک کردن پورت برنامه
بهمنظور استفاده داینامیک از مقدار port
یک فایل با نام config.json
در مسیر اصلی پروژه ایجاد کرده و port
را مقدار دهی میکنیم:
{
"port": 4050
}
در مرحلهی بعد، فایل www
را که در پوشهی bin
قرار دارد با استفاده از IDE خود باز کنید و کدهای زیر را:
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
به این شکل تغییر دهید:
var json = require('../config.json')
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(json.port || '3000');
app.set('port', port);
استفاده از OpenAPI در پروژه
برای شروع کار با APIها یک پوشهی جدید با نام api
در مسیر اصلی پروژه ایجاد کنید و فایلها را بهشکل زیر در آن قرار دهید:
├───api
│ │ api-doc.js
│ │
│ └───paths
│ └───todos
│ index.js
برای initialize کردن routeهای express-openapi
، کدهای زیر را به فایل app.js
اضافه کنید:
var { initialize } = require("express-openapi");
// OpenAPI routes
initialize({
app,
apiDoc: require("./api/api-doc"),
paths: "./api/paths",
});
پس از قرار دادن کدهای فوق در فایل app.js
، فایل api-doc.js
را که قبلتر در پوشهی api
قرار داده بودیم را با استفاده از IDE باز کرده و کدهای زیر را در آن قرار دهید:
const apiDoc = {
swagger: "2.0",
basePath: "/",
info: {
title: "Todo app API.",
version: "1.0.0",
},
definitions: {
Todo: {
type: "object",
properties: {
id: {
type: "number",
},
message: {
type: "string",
},
},
required: ["id", "message"],
},
},
paths: {},
};
module.exports = apiDoc;
پس از تعریف Schema کلی APIها برای مدیریت درخواستهای GET، POST و غیره بهسراغ تعریف Handlerها در فایل index.js
میرویم:
module.exports = function () {
let operations = {
GET,
POST,
PUT,
DELETE,
};
function GET(req, res, next) {
res.status(200).json([
{ id: 0, message: "First todo" },
{ id: 1, message: "Second todo" },
]);
}
function POST(req, res, next) {
console.log(`About to create todo: ${JSON.stringify(req.body)}`);
res.status(201).send();
}
function PUT(req, res, next) {
console.log(`About to update todo id: ${req.query.id}`);
res.status(200).send();
}
function DELETE(req, res, next) {
console.log(`About to delete todo id: ${req.query.id}`);
res.status(200).send();
}
GET.apiDoc = {
summary: "Fetch todos.",
operationId: "getTodos",
responses: {
200: {
description: "List of todos.",
schema: {
type: "array",
items: {
$ref: "#/definitions/Todo",
},
},
},
},
};
POST.apiDoc = {
summary: "Create todo.",
operationId: "createTodo",
consumes: ["application/json"],
parameters: [
{
in: "body",
name: "todo",
schema: {
$ref: "#/definitions/Todo",
},
},
],
responses: {
201: {
description: "Created",
},
},
};
PUT.apiDoc = {
summary: "Update todo.",
operationId: "updateTodo",
parameters: [
{
in: "query",
name: "id",
required: true,
type: "string",
},
{
in: "body",
name: "todo",
schema: {
$ref: "#/definitions/Todo",
},
},
],
responses: {
200: {
description: "Updated ok",
},
},
};
DELETE.apiDoc = {
summary: "Delete todo.",
operationId: "deleteTodo",
consumes: ["application/json"],
parameters: [
{
in: "query",
name: "id",
required: true,
type: "string",
},
],
responses: {
200: {
description: "Delete",
},
},
};
return operations;
};
پس از مدیریت درخواستهای HTTP
مجددا به فایل app.js
برمیگردیم و پیکربندی Swagger را انجام میدهیم:
var swaggerUi = require("swagger-ui-express");
var json = require('./config.json')
var port = (json.port || '3000');
app.use(
"/api-documentation",
swaggerUi.serve,
swaggerUi.setup(null, {
swaggerOptions: {
url: `http://localhost:${port}/api-docs`,
},
})
);
فایل نهایی app.js
ما به شکل زیر است:
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var { initialize } = require("express-openapi");
var swaggerUi = require("swagger-ui-express");
var json = require('./config.json')
var port = (json.port || '3000');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// OpenAPI routes
initialize({
app,
apiDoc: require("./api/api-doc"),
paths: "./api/paths",
});
// OpenAPI UI
app.use(
"/api-documentation",
swaggerUi.serve,
swaggerUi.setup(null, {
swaggerOptions: {
url: `http://localhost:${port}/api-docs`,
},
})
);
module.exports = app;
استقرار پروژه بر روی لیارا
ایجاد یک برنامهی جدید
در ابتدا وارد حساب کاربریتان شوید و در تب برنامهها بر روی دکمهی ایجاد برنامه کلیک کنید.
در صفحهای که برای شما باز میشود باید نوع برنامه و یک شناسهی یکتا برای برنامه درنظر بگیرید.
و در آخرین مرحلهی ایجاد یک برنامهی جدید از شما خواسته میشود که پلن مناسب را بهنسبت زیرساخت مورد نیاز انتخاب کرده و در نهایت بر روی دکمهی ایجاد برنامه کلیک کنید.
نصب Liara CLI
بلافاصله پس از ایجاد برنامهی جدید وارد صفحهی راهنما میشوید که دستورالعملهای استقرار برنامه بهصورت متنی و ویدیویی در آن قرار گرفته است.
طبق دستورالعملهای موجود در صفحهی راهنما باید Liara CLI را با اجرای دستور زیر نصب کنیم:
npm install -g @liara/cli
اما قبل از نصب Liara CLI با اجرای دستور npm -v
اطمینان حاصل کنید که این Package Manager بر روی سیستم شما نصب شده باشد.
ورود به حساب کاربری با Liara CLI
برای ورود به حساب کاربری میتوانید پس از نصب Liara CLI با اجرای دستور liara login
وارد حساب کاربری خود شوید اما نکتهای وجود دارد که باید به آن توجه داشته باشید.
همانطور که میدانید برای دسترسی به سرویسها در موقعیت ایران یا آلمان باید حساب کاربری مجزایی داشته باشید بنابراین در زمان ورود به حساب کاربریتان باید موقعیت جغرافیایی حساب خود را مشخص کنید.
پس از انتخاب موقعیت جغرافیایی باید ایمیلی که حساب کاربری خود را با آن ایجاد کردهاید، وارد کنید.
و درنهایت نوبت به وارد کردن رمزعبور میرسد که اگر ورود شما موفقیت آمیز باشد پیام You have logged in successfully.
به شما نمایش داده خواهد شد.
پیکربندی فایل liara.json و استقرار پروژه
دو روش برای استقرار پروژه در لیارا وجود دارد که در روش اول با استفاده از Terminal سیستمعامل خود وارد مسیر اصلی پروژه میشوید و دستور liara deploy
را اجرا میکنید اما باید توجه داشته باشید که با هر بار اجرای این دستور از شما خواسته میشود نام برنامهی ایجاد شده در لیارا را انتخاب و پورت برنامه را وارد کنید.
در روش دوم فقط با یک بار پیکربندی فایلی با نام liara.json
در مسیر اصلی پروژه و مشخص کردن مقادیر platform
، app
و port
دیگر نیازی نیست تا با هر بار اجرای دستور liara deploy
، نام برنامه و پورت آن را وارد کنیم.
اگر فراموش نکرده باشید در مراحل قبل یک برنامه با پلتفرم node
و شناسهی express-openapi
ایجاد کردیم و پورت 4050
را در فایل config.json
مشخص کردیم. حال با قرار دادن این دادهها در کنار هم، فایل liara.json
نهایی ما به شکل زیر خواهد بود:
{
"platform": "node",
"app": "express-openapi",
"port": 4050
}
پس از ذخیرهی فایل liara.json
میتوانید دستور liara deploy
را اجرا کنید تا برنامه بهصورت خودکار بر روی لیارا مستقر شود.
منبع: https://www.freecodecamp.org/news/how-to-build-explicit-apis-with-openapi