با Hapi و یک خرج TypeScript یک Jamstack REST API بسازید

Jamstack یک روش خوب برای جداسازی قسمت جلویی از انتهای عقب به جایی که کل راه حل لازم نیست در یک مونولیت قرار گیرد – و دقیقاً همزمان. وقتی Jamstack با REST API جفت شود ، مشتری و API می توانند پیشرفت کنند به طور مستقل. این بدان معنی است که هر دو انتهای جلو و عقب کاملاً بهم پیوسته نیستند و تغییر یکی لزوماً به معنی تغییر دیگری نیست.

در این مقاله ، نگاهی به REST API از منظر Jamstack خواهم انداخت. من نشان خواهم داد که چگونه API را بدون شکستن مشتریهای موجود تکمیل کرده و به استانداردهای REST پایبند هستم. من Hapi را به عنوان ابزار انتخابی برای ساخت API و Joi را برای اعتبارسنجی های نقطه پایانی انتخاب می کنم. لایه ماندگاری پایگاه داده برای دسترسی به داده ها از طریق Mongoose در MongoDB قرار می گیرد. توسعه آزمون محور به من کمک می کند تا از طریق تغییرات تکرار کنم و راهی سریع برای دریافت بازخورد با بار شناختی کمتر فراهم می کنم. در پایان ، هدف این است که شما ببینید چطور REST و Jamstack می توانند یک راه حل با انسجام بالا و اتصال کم بین ماژول های نرم افزار ارائه دهند. این نوع معماری برای سیستم های توزیع شده با تعداد بسیار زیادی سرویس ریز در دامنه های جداگانه خود بهترین است. من فرض می کنم یک دانش عملی از NPM ، ES6 + و یک آشنایی اساسی با نقاط پایانی API دارم.

API با داده های نویسنده ، با یک نام ، ایمیل و یک اختیاری 1: N کار می کند (یک به چند از طریق تعبیه سند) رابطه در مورد موضوعات مورد علاقه. من یک نقطه GET ، PUT (با یک افزودن) و DELETE می نویسم. برای تست API ، هر کلاینتی که پشتیبانی می کند fetch() انجام خواهد داد ، بنابراین من انتخاب می کنم هاپچتک و CURL

من جریان خواندن این قطعه را مانند یک آموزش که در آن می توانید از بالا به پایین دنبال کنید ، حفظ می کنم. برای کسانی که ترجیح می دهند از کد رد شوند ، این است موجود در GitHub برای لذت دیدن شما این آموزش فرض می کند که یک نسخه از Node (ترجیحاً آخرین LTS) و MongoDB از قبل نصب شده وجود دارد.

راه اندازی اولیه

برای شروع پروژه از ابتدا ، یک پوشه ایجاد کنید و cd در آن:

mkdir hapi-authors-rest-api
cd hapi-authors-rest-api

پس از ورود به پوشه پروژه ، آتش بزنید npm init و دستورالعمل را دنبال کنید. این یک ایجاد می کند package.json در ریشه پوشه قرار دارد.

هر پروژه Node وابستگی هایی دارد. برای شروع به Hapi ، Joi و Mongoose نیاز دارم:

npm i @hapi/hapi joi mongoose --save-exact

بازرسی package.json تا اطمینان حاصل کنید که تمام وابستگی ها و تنظیمات پروژه وجود دارد. سپس ، یک نقطه ورود به این پروژه اضافه کنید:

"scripts": {
  "start": "node index.js"
},

ساختار پوشه MVC با نسخه

برای این REST API ، من از یک ساختار پوشه MVC معمولی با کنترل کننده ها ، مسیرها و یک مدل پایگاه داده استفاده می کنم. کنترلر نسخه ای مانند آن را خواهد داشت AuthorV1Controller اجازه می دهد تا API هنگامی که تغییرات شکستنی در مدل ایجاد می شود ، تکامل یابد. هاپی یک server.js و index.js برای آزمایش این پروژه از طریق توسعه آزمون محور. test پوشه شامل آزمایشات واحد خواهد بود.

در زیر ساختار پوشه کلی وجود دارد:

┳
┣━┓ config
┃ ┣━━ dev.json
┃ ┗━━ index.js
┣━┓ controllers
┃ ┗━━ AuthorV1Controller.js
┣━┓ model
┃ ┣━━ Author.js
┃ ┗━━ index.js
┣━┓ routes
┃ ┣━━ authors.js
┃ ┗━━ index.js
┣━┓ test
┃ ┗━━ Author.js
┣━━ index.js
┣━━ package.json
┗━━ server.js

در حال حاضر ، ادامه دهید و پوشه ها و پرونده های مربوطه را در داخل هر پوشه ایجاد کنید.

mkdir config controllers model routes test
touch config/dev.json config/index.js controllers/AuthorV1Controller.js model/Author.js model/index.js routes/authors.js routes/index.js test/Authors.js index.js server.js

این همان چیزی است که هر پوشه برای آن در نظر گرفته شده است:

  • config: اطلاعات پیکربندی برای اتصال به اتصال Mongoose و سرور Hapi.
  • controllers: اینها کنترل کننده های Hapi هستند که با اشیا Re درخواست / پاسخ سروکار دارند. نسخه دهی به چندین نقطه پایان برای هر شماره نسخه اجازه می دهد – یعنی /v1/authors، /v2/authors، و غیره.
  • model: به پایگاه داده MongoDB متصل می شود و طرح Mongoose را تعریف می کند.
  • routes: نقاط پایانی را با اعتبار سنجی Joi برای خالصان REST تعریف می کند.
  • test: تست واحد از طریق ابزار آزمایشگاهی Hapi. (بعداً در این باره بیشتر توضیح دهید)

در یک پروژه واقعی ، انتزاع منطق مشترک کسب و کار در یک پوشه جداگانه ، ممکن است مفید باشد utils. من توصیه می کنم ایجاد کنید AuthorUtil.js ماژولی با کد کاملاً کاربردی برای استفاده مجدد از آن در نقاط انتهایی و آزمایش واحد برای آن آسان است. از آنجا که این راه حل منطق تجاری پیچیده ای ندارد ، من تصمیم می گیرم از این پوشه صرف نظر کنم.

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

TypeScript

برای بهبود تجربه توسعه دهنده ، اکنون اعلامیه های نوع TypeScript را اضافه می کنم. از آنجا که Mongoose و Joi مدل را هنگام اجرا تعریف می کنند ، افزودن یک نوع جستجوگر در زمان کامپایل ارزش کمی دارد. در TypeScript ، می توان تعاریف نوع را به یک پروژه جاوا اسکریپت وانیلی اضافه کرد و همچنان از مزایای نوع جستجوگر در ویرایشگر کد بهره مند شد. ابزارهایی مانند WebStorm یا VS Code تعاریف نوع را انتخاب کرده و به برنامه نویس اجازه می دهد تا “کد” را درون کد قرار دهد. این روش اغلب نامیده می شود IntelliSense، و هنگامی فعال می شود که IDE انواع در دسترس را داشته باشد. آنچه با این روش بدست می آورید یک روش خوب برای تعریف رابط برنامه نویسی است تا توسعه دهندگان بدون مشاهده اسناد و مدارک به اشیا dot بپردازند. ویرایشگر نیز گاهی اوقات هشدارهایی را نشان می دهد وقتی که توسعه دهندگان به یک موضوع اشتباه نگاه می کنند.

این چیزی است که IntelliSense در VS Code به نظر می رسد:

VSCode IntelliSense

در WebStorm ، این را تکمیل کد می نامند ، اما در اصل همان چیز است. در صورت تمایل کدامیک از IDE ها را برای نوشتن کد انتخاب کنید. من از Vim و WebStorm استفاده می کنم ، اما شما ممکن است متفاوت انتخاب کنید.

برای فعال کردن اعلامیه های نوع TypeScript در این پروژه ، NPM را خاموش کرده و این وابستگی های توسعه دهنده را ذخیره کنید:

npm i @types/hapi @types/mongoose --save-dev

من توصیه می کنم وابستگی های توسعه دهنده را از وابستگی های برنامه جدا نگه دارید. به این ترتیب ، برای سایر توسعه دهندگان سازمان مشخص است که بسته ها برای چه منظور هستند. هنگامی که یک سرور build ، repo را پایین می آورد ، همچنین این گزینه را دارد که از بسته هایی که پروژه در زمان اجرا به آنها نیازی ندارد ، بگذرد.

با در دست داشتن همه نکات خوب توسعه دهنده ، اکنون زمان شروع نوشتن کد است. هاپی را باز کنید server.js سرور اصلی را وارد کنید:

const config = require('./config')
const routes = require('./routes')
const db = require('./model')
const Hapi = require('@hapi/hapi')

const server = Hapi.server({
  port: config.APP_PORT,
  host: config.APP_HOST,
  routes: {
    cors: true
  }
})

server.route(routes)

exports.init = async () => {
  await server.initialize()
  await db.connect()
  return server
}

exports.start = async () => {
  await server.start()
  await db.connect()
  console.log(`Server running at: ${server.info.uri}`)
  return server
}

process.on('unhandledRejection', (err) => {
  console.error(err)
  process.exit(1)
})

من CORS را با تنظیم فعال کردم cors درست است بنابراین این REST API می تواند با Hoppscotch کار کند.

برای ساده نگه داشتن آن ، از نقطه ویرگول در این پروژه چشم پوشی می کنم. رد شدن از ساخت TypeScript در این پروژه و تایپ کردن آن شخصیت اضافی تا حدودی آزاد است. این از Mantra Hapi پیروی می کند ، زیرا به هر حال همه چیز در مورد خوشبختی توسعه دهنده است.

زیر config/index.js، حتما صادر کنید dev.json اطلاعات:

module.exports = require('./dev')

برای استفاده از پیکربندی سرور ، این مورد را وارد کنید dev.json:

{
  "APP_PORT": 3000,
  "APP_HOST": "127.0.0.1"
}

اعتبار سنجی بقیه

برای حفظ نقاط پایانی REST مطابق با استانداردهای HTTP ، اعتبارسنجی های Joi را اضافه می کنم. این اعتبار سنجی ها به جدا شدن API از مشتری کمک می کند ، زیرا باعث یکپارچگی منابع می شود. برای Jamstack ، این بدان معناست که مشتری دیگر به جزئیات پیاده سازی پشت هر منبع اهمیت نمی دهد. درمان هر نقطه پایانی بصورت مستقل رایگان است ، زیرا اعتبار سنجی درخواست معتبری از منبع را تضمین می کند. رعایت یک استاندارد سخت گیرانه HTTP باعث می شود مشتری براساس یک منبع هدف که در پشت مرز HTTP قرار دارد تکامل یابد و این جدا سازی را اعمال می کند. در واقع ، هدف استفاده از نسخه ها و اعتبار سنجی ها برای حفظ مرز پاک در Jamstack است.

با REST ، هدف اصلی حفظ است کم کاری با روشهای GET ، PUT و DELETE. اینها روشهای درخواست ایمن هستند زیرا درخواستهای بعدی به همان منبع هیچ گونه عوارض جانبی ندارند. همان اثر مورد نظر تکرار می شود حتی اگر مشتری نتواند ارتباط برقرار کند.

من می خواهم از POST و PATCH بگذرم ، زیرا این روش های ایمن نیستند. این امر به دلیل اختصار و بی کفایتی است ، اما نه به این دلیل که این روش ها مشتری را به هر طریقی محکم می کنند. همین استانداردهای سختگیرانه HTTP می تواند در مورد این روشها اعمال شود ، با این تفاوت که اینها ضعف در کار بودن را تضمین نمی کنند.

که در routes/authors.js، اعتبار سنجی Joi زیر را اضافه کنید:

const Joi = require('joi')

const authorV1Params = Joi.object({
  id: Joi.string().required()
})

const authorV1Schema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string().email().required(),
  topics: Joi.array().items(Joi.string()), // optional
  createdAt: Joi.date().required()
})

توجه داشته باشید که هر تغییری در مدل نسخه شده احتمالاً به نسخه جدیدی مانند a نیاز دارد v2. این قابلیت سازگاری معکوس را برای مشتریان موجود تضمین می کند و به API اجازه می دهد تا به طور مستقل تکامل یابد. در صورت عدم وجود زمینه ، قسمتهای مورد نیاز با پاسخ 400 (درخواست بد) درخواست را رد می کنند.

با تأیید اعتبار برنامه و برنامه ، مسیرهای واقعی را به این منبع اضافه کنید:

// routes/authors.js
const v1Endpoint = require('../controllers/AuthorV1Controller')

module.exports = [{
  method: 'GET',
  path: '/v1/authors/{id}',
  handler: v1Endpoint.details,
  options: {
    validate: {
      params: authorV1Params
    },
    response: {
      schema: authorV1Schema
    }
  }
}, {
  method: 'PUT',
  path: '/v1/authors/{id}',
  handler: v1Endpoint.upsert,
  options: {
    validate: {
      params: authorV1Params,
      payload: authorV1Schema
    },
    response: {
      schema: authorV1Schema
    }
  }
}, {
  method: 'DELETE',
  path: '/v1/authors/{id}',
  handler: v1Endpoint.delete,
  options: {
    validate: {
      params: authorV1Params
    }
  }
}]

برای اینکه این مسیرها در دسترس باشد server.js، این را به اضافه کنید routes/index.js:

module.exports = [
  ...require('./authors')
]

اعتبار سنجی های Joi در options قسمت آرایه مسیرها. هر مسیر درخواست یک پارامتر شناسه رشته ای را دارد که با آن مطابقت دارد ObjectId در MongoDB. این id بخشی از مسیر نسخه شده است زیرا منبع هدف مشتری برای کار با آن است. برای یک PUT ، اعتبار باربری وجود دارد که با پاسخ GET مطابقت دارد. این برای رعایت استانداردهای REST است پاسخ PUT باید با GET بعدی مطابقت داشته باشد.

این همان چیزی است که در استاندارد می گوید:

قرار دادن موفقیت آمیز یک نمایش داده شده نشان می دهد که یک GET بعدی در همان منبع مورد نظر منجر به ارسال نمایندگی معادل 200 (تأیید) می شود.

این باعث می شود که یک PUT از به روزرسانی های جزئی پشتیبانی نکند زیرا GET بعدی با PUT مطابقت ندارد. برای Jamstack ، رعایت استانداردهای HTTP برای اطمینان از قابل پیش بینی بودن و جداسازی مشتری بسیار مهم است.

AuthorV1Controller درخواست را از طریق یک متد کنترل کننده در اداره می کند v1Endpoint. داشتن یک کنترل کننده برای هر نسخه ایده خوبی است ، زیرا این همان چیزی است که پاسخ را به مشتری برمی گرداند. این امر باعث می شود که توسعه API از طریق یک کنترل کننده نسخه جدید بدون شکستن کلاینت های موجود ، آسان تر شود.

ادامه مطالعه ساخت API استراحت برای Jamstack با Hapi و TypeScript در SitePoint.