برنامه‌نویسی

آموزش ایجاد RESTful API با Express و OpenAPI


۳ اردیبهشت ۱۴۰۰
آموزش ایجاد 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

بلافاصله پس از ایجاد برنامه‌ی جدید وارد صفحه‌ی راهنما می‌شوید که دستورالعمل‌های استقرار برنامه به‌صورت متنی و ویدیویی در آن قرار گرفته است.

صفحه‌ی راهنمای برنامه‌های node در لیارا

طبق دستورالعمل‌های موجود در صفحه‌ی راهنما باید Liara CLI را با اجرای دستور زیر نصب کنیم:

npm install -g @liara/cli

اما قبل از نصب Liara CLI با اجرای دستور npm -v اطمینان حاصل کنید که این Package Manager بر روی سیستم شما نصب شده باشد.

ورود به حساب کاربری با Liara CLI

برای ورود به حساب کاربری می‌توانید پس از نصب Liara CLI با اجرای دستور liara login وارد حساب کاربری خود شوید اما نکته‌ای وجود دارد که باید به آن‌ توجه داشته باشید.

همان‌طور که می‌دانید برای دسترسی به سرویس‌ها در موقعیت ایران یا آلمان باید حساب کاربری مجزایی داشته باشید بنابراین در زمان ورود به حساب کاربریتان باید موقعیت جغرافیایی حساب خود را مشخص کنید.

انتخاب بین موقعیت‌های جغرافیایی ایران و آلمان در liara cli

پس از انتخاب موقعیت جغرافیایی باید ایمیلی که حساب کاربری خود را با آن ایجاد کرده‌اید، وارد کنید.

وارد کردن ایمیل و رمزعبور در liara cli

و درنهایت نوبت به وارد کردن رمزعبور می‌رسد که اگر ورود شما موفقیت آمیز باشد پیام You have logged in successfully. به شما نمایش داده خواهد شد.

ورود موفقیت‌آمیر در حساب کاربری لیارا با استفاده از liara cli

پیکربندی فایل 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