إنشاء API دينو صغيرة

في هذا المنشور ، أود أن أقول وأظهر عملية إنشاء واجهة برمجة تطبيقات صغيرة باستخدام Deno. Deno هو أحدث مشغل Javascript و Typescript تم تطويره بواسطة منشئ Node.js رايان دال.



أهدافنا :

  • تطوير واجهة برمجة تطبيقات تعمل مع بيانات المستخدم
  • توفير القدرة على استخدام أساليب GET و POST و PUT و DELETE
  • حفظ وتحديث بيانات المستخدم في ملف JSON المحلي
  • استخدم الإطار لتسريع التطور

الشيء الوحيد الذي نحتاج إلى تثبيته هو Deno. يدعم Deno Typescript فور إخراج الجهاز من العلبة. في هذا المثال ، استخدمت الإصدار 0.22 من Deno وقد لا يعمل هذا الرمز مع الإصدارات المستقبلية.
يمكن العثور على إصدار Deno المثبت باستخدام الأمر deno version في الطرفية.

هيكل البرنامج


handlers
middlewares
models
services
config.ts
index.ts
routing.ts

كما ترى ، يبدو أنه تطبيق ويب صغير على Node.js.

  • معالجات - تحتوي على معالجات توجيه
  • الوسيطة - تحتوي على الوظائف التي سيتم إطلاقها في كل طلب
  • النماذج - تحتوي على تعيين النماذج ، وفي حالتنا هذه هي فقط واجهة مستخدم
  • خدمات - تحتوي على ... خدمات!
  • config.ts - ملف تكوين التطبيق
  • index.ts هي نقطة الدخول لتطبيقنا
  • Routing.ts - يحتوي على توجيهات API

اختيار الإطار


هناك العديد من أطر العمل الرائعة لـ Node.js. أحد أشهرها هو Express . هناك أيضًا نسخة حديثة من Express'a - Koa . لسوء الحظ ، لا يدعم Deno مكتبات Node.js والخيار أصغر كثيرًا ، ولكن هناك إطار عمل لـ Deno ، والذي يعتمد على Koa - Oak . سنستخدمها لمشروعنا الصغير. إذا لم تستخدم Koa مطلقًا ، فلا تقلق ، فهي تشبه Express كثيرًا.

إنشاء نقطة إدخال للتطبيق


index.ts

import { Application } from "https://deno.land/x/oak/mod.ts";
import { APP_HOST, APP_PORT } from "./config.ts";
import router from "./routing.ts";
import notFound from "./handlers/notFound.ts";
import errorMiddleware from "./middlewares/error.ts";

const app = new Application();

app.use(errorMiddleware);
app.use(router.routes());
app.use(router.allowedMethods());
app.use(notFound);

console.log(`Listening on ${APP_PORT}...`);

await app.listen(`${APP_HOST}:${APP_PORT}`);

في السطر الأول ، نستخدم إحدى شرائح دينو الرئيسية - استيراد الوحدات مباشرة من الإنترنت. بعد ذلك ، لا يوجد شيء غير عادي: نقوم بإنشاء تطبيق ، وإضافة برامج وسيطة ، ومسارات ، وأخيرًا ، بدء تشغيل الخادم. كل شيء هو نفسه عند استخدام Express أو Koa.

إنشاء التكوين


config.ts
const env = Deno.env();
export const APP_HOST = env.APP_HOST || "127.0.0.1";
export const APP_PORT = env.APP_PORT || 4000;
export const DB_PATH = env.DB_PATH || "./db/users.json";

ملف التكوين الخاص بنا. يتم نقل الإعدادات من بيئة التشغيل ، ولكننا سنضيف أيضًا القيم الافتراضية. و Deno.env () وظيفة هو التناظرية من process.env في Node.js.

إضافة نموذج مستخدم


النماذج / user.ts

export interface User {
  id: string;
  name: string;
  role: string;
  jiraAdmin: boolean;
  added: Date;
}

إنشاء ملف توجيه


router.ts

import { Router } from "https://deno.land/x/oak/mod.ts";

import getUsers from "./handlers/getUsers.ts";
import getUserDetails from "./handlers/getUserDetails.ts";
import createUser from "./handlers/createUser.ts";
import updateUser from "./handlers/updateUser.ts";
import deleteUser from "./handlers/deleteUser.ts";

const router = new Router();

router
  .get("/users", getUsers)
  .get("/users/:id", getUserDetails)
  .post("/users", createUser)
  .put("/users/:id", updateUser)
  .delete("/users/:id", deleteUser);

export default router;

مرة أخرى ، لا يوجد شيء غير عادي. أنشأنا جهاز توجيه وأضفنا إليه عدة مسارات. يبدو أنك قد نسخت رمزًا من تطبيق Express ، أليس كذلك؟

معالجة الأحداث للطرق


معالجات / getUsers.ts

import { getUsers } from "../services/users.ts";

export default async ({ response }) => {
  response.body = await getUsers();
};

إرجاع جميع المستخدمين. إذا لم تستخدم Koa من قبل ، فسأشرح لك. يشبه كائن الاستجابة الدقة في Express. يحتوي الكائن res في Express على طريقتين مثل json أو send ، تُستخدم لإرسال استجابة. في Oak و Koa ، نحتاج إلى تعيين القيمة التي نريد أن نعود بها إلى response.body property .

معالجات / getUserDetails.ts

import { getUser } from "../services/users.ts";

export default async ({ params, response }) => {
  const userId = params.id;

  if (!userId) {
    response.status = 400;
    response.body = { msg: "Invalid user id" };
    return;
  }

  const foundUser = await getUser(userId);
  if (!foundUser) {
    response.status = 404;
    response.body = { msg: `User with ID ${userId} not found` };
    return;
  }

  response.body = foundUser;
};

كل شيء سهل هنا أيضًا. يقوم المعالج بإرجاع المستخدم بالمعرف المطلوب.

معالجات / createUser.ts

import { createUser } from "../services/users.ts";

export default async ({ request, response }) => {
  if (!request.hasBody) {
    response.status = 400;
    response.body = { msg: "Invalid user data" };
    return;
  }

  const {
    value: { name, role, jiraAdmin }
  } = await request.body();

  if (!name || !role) {
    response.status = 422;
    response.body = { msg: "Incorrect user data. Name and role are required" };
    return;
  }

  const userId = await createUser({ name, role, jiraAdmin });

  response.body = { msg: "User created", userId };
};

هذا المعالج مسؤول عن إنشاء المستخدم.

معالجات / updateUser.ts

import { updateUser } from "../services/users.ts";

export default async ({ params, request, response }) => {
  const userId = params.id;

  if (!userId) {
    response.status = 400;
    response.body = { msg: "Invalid user id" };
    return;
  }

  if (!request.hasBody) {
    response.status = 400;
    response.body = { msg: "Invalid user data" };
    return;
  }

  const {
    value: { name, role, jiraAdmin }
  } = await request.body();

  await updateUser(userId, { name, role, jiraAdmin });

  response.body = { msg: "User updated" };
};

يقوم المعالج بالتحقق من وجود المستخدم بالمعرف المحدد وتحديث بيانات المستخدم.

معالجات / deleteUser.ts

import { deleteUser, getUser } from "../services/users.ts";

export default async ({ params, response }) => {
  const userId = params.id;

  if (!userId) {
    response.status = 400;
    response.body = { msg: "Invalid user id" };
    return;
  }

  const foundUser = await getUser(userId);
  if (!foundUser) {
    response.status = 404;
    response.body = { msg: `User with ID ${userId} not found` };
    return;
  }

  await deleteUser(userId);
  response.body = { msg: "User deleted" };
};

المسؤول عن حذف مستخدم.

من المستحسن أيضًا معالجة طلبات التوجيهات غير الموجودة وإرجاع رسالة خطأ.

معالجات / notFound.ts

export default ({ response }) => {
  response.status = 404;
  response.body = { msg: "Not Found" };
};

إضافة خدمات


قبل إنشاء الخدمات التي ستعمل مع بيانات المستخدم ، نحتاج إلى إنشاء خدمتين إضافيتين صغيرتين.

services / createId.ts

import { v4 as uuid } from "https://deno.land/std/uuid/mod.ts";

export default () => uuid.generate();

سيحصل كل مستخدم جديد على معرف فريد. سنستخدم وحدة uuid من مكتبة Deno القياسية لإنشاء رقم عشوائي.

services / db.ts

import { DB_PATH } from "../config.ts";
import { User } from "../models/user.ts";

export const fetchData = async (): Promise<User[]> => {
  const data = await Deno.readFile(DB_PATH);

  const decoder = new TextDecoder();
  const decodedData = decoder.decode(data);

  return JSON.parse(decodedData);
};

export const persistData = async (data): Promise<void> => {
  const encoder = new TextEncoder();
  await Deno.writeFile(DB_PATH, encoder.encode(JSON.stringify(data)));
};

ستساعد هذه الخدمة على التفاعل مع ملف JSON الذي سيتم تخزين بيانات المستخدم فيه.
للحصول على جميع المستخدمين ، اقرأ محتويات الملف. تعرض الدالة readFile كائنًا من النوع Uint8Array ، والذي يجب تحويله إلى نوع String قبل الدخول إلى ملف JSON .

وأخيرًا ، الخدمة الرئيسية للعمل مع بيانات المستخدم.

services / users.ts

import { fetchData, persistData } from "./db.ts";
import { User } from "../models/user.ts";
import createId from "../services/createId.ts";

type UserData = Pick<User, "name" | "role" | "jiraAdmin">;

export const getUsers = async (): Promise<User[]> => {
  const users = await fetchData();

  // sort by name
  return users.sort((a, b) => a.name.localeCompare(b.name));
};

export const getUser = async (userId: string): Promise<User | undefined> => {
  const users = await fetchData();

  return users.find(({ id }) => id === userId);
};

export const createUser = async (userData: UserData): Promise<string> => {
  const users = await fetchData();

  const newUser: User = {
    id: createId(),
    name: String(userData.name),
    role: String(userData.role),
    jiraAdmin: "jiraAdmin" in userData ? Boolean(userData.jiraAdmin) : false,
    added: new Date()
  };

  await persistData([...users, newUser]);

  return newUser.id;
};

export const updateUser = async (
  userId: string,
  userData: UserData
): Promise<void> => {
  const user = await getUser(userId);

  if (!user) {
    throw new Error("User not found");
  }

  const updatedUser = {
    ...user,
    name: userData.name !== undefined ? String(userData.name) : user.name,
    role: userData.role !== undefined ? String(userData.role) : user.role,
    jiraAdmin:
      userData.jiraAdmin !== undefined
        ? Boolean(userData.jiraAdmin)
        : user.jiraAdmin
  };

  const users = await fetchData();
  const filteredUsers = users.filter(user => user.id !== userId);

  persistData([...filteredUsers, updatedUser]);
};

export const deleteUser = async (userId: string): Promise<void> => {
  const users = await getUsers();
  const filteredUsers = users.filter(user => user.id !== userId);

  persistData(filteredUsers);
};

يوجد الكثير من التعليمات البرمجية هنا ، ولكن هذا هو نسخة مطبوعة خالصة.

معالجة الخطأ


ما الذي يمكن أن يكون أسوأ مما قد يحدث في حالة حدوث خطأ في الخدمة مع بيانات المستخدم؟ قد يتلف البرنامج بأكمله. لتجنب هذا السيناريو ، يمكنك استخدام بناء المحاولة / الالتقاط في كل معالج. ولكن هناك حل أكثر أناقة - أضف الوسيطة أمام كل مسار ومنع أي أخطاء غير متوقعة قد تحدث.

middlewares / error.ts

export default async ({ response }, next) => {
  try {
    await next();
  } catch (err) {
    response.status = 500;
    response.body = { msg: err.message };
  }
};

قبل بدء البرنامج نفسه ، نحتاج إلى إضافة البيانات للتشغيل.

ديسيبل / users.json

[
  {
    "id": "1",
    "name": "Daniel",
    "role": "Software Architect",
    "jiraAdmin": true,
    "added": "2017-10-15"
  },
  {
    "id": "2",
    "name": "Markus",
    "role": "Frontend Engineer",
    "jiraAdmin": false,
    "added": "2018-09-01"
  }
]

هذا كل شئ! الآن يمكنك محاولة تشغيل تطبيقنا:

deno -A index.ts

علامة "A" تعني أن البرنامج لا يحتاج إلى منح أي أذونات منفصلة. لا تنس أن استخدام هذا العلم غير آمن في الإنتاج.

على الأرجح ، سترى العديد من الأسطر مع التنزيل (تنزيل) وتجميع (ترجمة). في النهاية ، يجب أن يظهر الخط المرغوب:

Listening on 4000

كي تختصر


ما الأدوات التي استخدمناها؟

  1. كائن عام Deno لقراءة / كتابة الملفات
  2. uuid من مكتبة دينو القياسية لإنشاء معرف فريد
  3. البلوط - إطار عمل خارجي مستوحى من Koa لـ Node.js
  4. كائنات Typescript أو TextEncode أو JSON المضمنة في جافا سكريبت

ما الفرق من Node.js؟


  • لا حاجة لتثبيت وتكوين برنامج التحويل البرمجي لـ Typescript أو أدوات أخرى مثل عقدة ts. يمكنك ببساطة تشغيل البرنامج باستخدام الأمر deno index.ts
  • إدراج جميع وحدات الطرف الثالث مباشرة في التعليمات البرمجية دون الحاجة إلى التثبيت الأولي
  • عدم وجود package.json و package-lock.json
  • عدم وجود node_modules في الدليل الجذر لبرنامجنا. توجد جميع الملفات التي تم تنزيلها في ذاكرة التخزين المؤقت العامة

إذا لزم الأمر ، يمكن العثور على رمز المصدر هنا .

Source: https://habr.com/ru/post/undefined/


All Articles