ہونو اور زوڈ کا استعمال کرتے ہوئے ٹائپ سیف APIs کیسے بنائیں

اگر آپ نے پہلے کبھی Node.js API جاری کیا ہے، تو شاید آپ ان چیلنجوں سے پہلے ہی واقف ہوں گے۔ TypeScript کی قسمیں ایک بات کہتی ہیں، رن ٹائم کی توثیق ایک اور کہتی ہے، اور OpenAPI دستاویزات خاموشی سے دونوں سے متفق نہیں ہیں۔

کسی نے انٹرفیس میں ایک فیلڈ شامل کیا اور اسکیما اپ ڈیٹ نہیں ہوا۔ دستاویز اس وقت تک پرانی رہے گی جب تک کہ کوئی کلائنٹ بگ کی اطلاع نہیں دیتا۔ کوئی کمپائلر غلطیاں یا ناکام ٹیسٹ نہیں ہیں۔ سچائی کے تین ماخذ ایک دوسرے سے الگ ہیں۔

اس ٹیوٹوریل میں، آپ سیکھیں گے کہ ہونو اور زوڈ کا استعمال کرتے ہوئے تین مسائل کو ایک تعریف میں کیسے کم کیا جائے۔ آپ وہی پیٹرن بنائیں گے جو میں پروڈکشن میں استعمال کرتا ہوں، بشمول ClipForge، ایک اوپن سورس ویڈیو پروسیسنگ ٹول کٹ جسے میں نے بنایا اور برقرار رکھا۔ اس سے توثیق، اقسام اور دستاویزات کو ڈیزائن کے لحاظ سے مطابقت پذیر رکھنے میں مدد ملتی ہے۔

اس مضمون کے اختتام تک، آپ کو معلوم ہونا چاہیے کہ کیسے:

  • ایک Zod اسکیما کی وضاحت کریں جو رن ٹائم کی توثیق، TypeScript کی اقسام، اور OpenAPI دستاویزات کو ہینڈل کرتا ہے

  • اپنے راستوں کی ساخت بنائیں تاکہ معاہدے اور ہینڈلرز الگ رہیں۔

  • ڈیٹا بیس اسکیما اور HTTP اسکیما کو دو جان بوجھ کر پرتوں کے طور پر رکھنا

  • تمام ناکامی کے راستے ایک مستقل غلطی کی شکل واپس کرتے ہیں۔

  • اسی پیٹرن کو چھوٹے ٹاسکس API سے حقیقی ملٹی سروس سسٹم میں منتقل کرنا

شرائط

اس مضمون سے زیادہ سے زیادہ فائدہ اٹھانے کے لیے، آپ کو یہ جاننے کی ضرورت ہے:

  • جاوا اسکرپٹ اور بنیادی ٹائپ اسکرپٹ

  • REST API کیسے کام کرتا ہے (راستہ، درخواست کا باڈی، اسٹیٹس کوڈ)

  • تھوڑا سا Node.js (پیکجز انسٹال کریں، اسکرپٹ چلائیں)

ہونو، زوڈ، یا بوندا باندی کے ساتھ کسی پیشگی تجربے کی ضرورت نہیں ہے۔

انڈیکس

1. بہاؤ کا مسئلہ

زیادہ تر TypeScript APIs ایک ہی ڈیٹا کی تین الگ الگ وضاحتیں برقرار رکھتے ہیں۔

  1. رن ٹائم توثیق چیک کرتا ہے جو درخواست آنے پر چلتا ہے۔

  2. ٹائپ اسکرپٹ کی اقسام شکل مرتب کرنے والا اسے تعمیر کے وقت سمجھتا ہے۔

  3. API دستاویزاتکنٹریکٹ صارفین کو دکھایا گیا ہے۔

ہر ایک مختلف فائل میں موجود ہے اور اسے مختلف شیڈول پر اپ ڈیٹ کیا جاتا ہے۔ اور ان میں سے کوئی بھی دوسرے کو نہیں دیکھ سکتا۔

شکل 1: ایک ہی ڈیٹا کی تین وضاحتیں الگ الگ اور اکثر مطابقت پذیر نہیں ہیں۔

ہاتھ سے لکھے ہوئے انٹرفیس رن ٹائم کے وقت غائب ہو جاتے ہیں۔ Joi یا Yup اسکیماس ڈیٹا کی توثیق کرتے ہیں، لیکن اقسام مفت فراہم نہیں کرتے ہیں۔ OpenAPI فائلیں عام طور پر دستی طور پر ترمیم کی جاتی ہیں، اگر بالکل بھی۔

حل "زیادہ محتاط رہنا” نہیں ہے۔ ترمیم ایک تعریف ہے جو تینوں آؤٹ پٹ تیار کرتی ہے۔

ایک Zod اسکیما جو رن ٹائم کی توثیق، TypeScript کی اقسام، اور OpenAPI دستاویزات تیار کرتا ہے۔

شکل 2: ایک اسکیما کی تعریف تین آؤٹ پٹ پیدا کرتی ہے۔

یہ وہی ہے جو Hono اور Zod ایک ساتھ استعمال کرنے پر پیش کرتے ہیں۔ @hono/zod-openapi.

2. ہونو کیا ہے؟

Hono ایک چھوٹا اور تیز ویب فریم ورک ہے جو Web Standard API پر بنایا گیا ہے۔ Request اور Response مقامی عناصر جو Node.js، Deno، Bun، اور Cloudflare ورکرز پر چلتے ہیں۔

ایکسپریس کے مقابلے میں، فرق اہم ہے۔

اظہار جڑیں
نوڈ کی طرف سے req / res ویب معیاری API
پیرامیٹر ایک غیر ٹائپ شدہ تار ہے۔ پیرامیٹرز کی تصدیق کی گئی اور Zod کا استعمال کرتے ہوئے درج کیا گیا۔
توثیق ایک مسئلہ ہے۔ راستے کی تعریف ایک OpenAPI اندراج ہے۔
صرف نوڈس پر چلائیں۔ نوڈ، بن، ڈینو اور ایج پر چلتا ہے۔

ہونو کا کور تقریباً 14kb ہے۔ نوڈ پر، یہ اسی کام کے بوجھ کے لیے ایکسپریس سے تقریباً 5 سے 7 گنا تیز ہے۔ Bun یا Cloudflare ورکرز کے ساتھ یہ خلا اور بھی وسیع ہے کیونکہ ان کے رن ٹائمز ویب معیارات کے لیے موزوں ہیں۔

زیادہ تر CRUD APIs کے لیے، ڈیٹا بیس اب بھی رکاوٹ ہے۔ تاہم، ایج رن ٹائمز میں جہاں ہم آہنگی زیادہ ہوتی ہے یا کولڈ سٹارٹس اہم ہوتے ہیں، فریم ورک کا فرق حقیقی ہو جاتا ہے۔

اس ٹیوٹوریل کے لیے زیادہ اہم بات یہ ہے کہ ہونو کا OpenAPI انضمام راستے کی تعریف کی اجازت دیتا ہے۔ بی ای دستاویز

3. زود کیا ہے؟

Zod ایک TypeScript کی پہلی اسکیما کی توثیق کی لائبریری ہے۔ بیان کریں کہ ڈیٹا کیسا لگتا ہے۔ پھر زود:

  1. رن ٹائم پر اس شکل کی توثیق کرتا ہے۔

  2. TypeScript قسم کا اندازہ۔ z.infer

  3. منسلک ہونے پر OpenAPI دستاویزات کو فیڈ کرتا ہے۔ .openapi() میٹا ڈیٹا

Joi یا Yup کے ساتھ، آپ عام طور پر رن ​​ٹائم پر توثیق کرتے ہیں اور پھر مماثل انٹرفیس خود لکھتے ہیں۔ یہ پھر دو تعریفیں ہیں۔ زود دوسرے کو ختم کرتا ہے۔

import { z } from 'zod';

const createTaskSchema = z.object({
  title: z.string().min(1).max(120),
  status: z.enum(['todo', 'in_progress', 'done']).default('todo'),
});

type CreateTaskInput = z.infer;
// { title: string; status?: "todo" | "in_progress" | "done" }

اسکیما اور اس پر منحصر تمام کال سائٹس کو تبدیل کریں۔ CreateTaskInput اس کے ساتھ اپ ڈیٹ کریں۔ TypeScript آپ کو بتائے گا کہ مسئلہ کیا ہے۔

4. ایک اسکیما، تین آپریشن

باقی مضمون کے لیے میرا ذہنی نمونہ یہ ہے:

ٹاسک اسکیما رن ٹائم توثیق، ٹائپ اسکرپٹ کی اقسام اور اوپن اے پی آئی دستاویزات فراہم کرتا ہے۔

تصویر 3: taskSchema توثیق، اقسام اور دستاویزات کے لیے سچائی کا واحد ذریعہ۔

  1. رن ٹائم توثیق: غلط ڈیٹا کو ہینڈلر تک پہنچنے سے پہلے اسٹیک ٹریس کی بجائے سٹرکچرڈ فیلڈ کی خرابی کے ساتھ مسترد کر دیا جاتا ہے۔

  2. ٹائپ اسکرپٹ کی اقسام: z.infer یہ اسکیما سے ماخوذ ہے اور اسے الگ نہیں رکھا گیا ہے۔

  3. OpenAPI دستاویزات: .openapi('Name') کیونکہ اسکیما تیار کردہ تفصیلات میں رجسٹرڈ ہے، /reference اپ ٹو ڈیٹ رہیں

تین آؤٹ پٹ کے ساتھ ایک تعریف۔ دستی طور پر مطابقت پذیری کے لیے کچھ نہیں ہے۔

5. پروجیکٹ کیسے ترتیب دیا جائے۔

آئیے api-conf-demo میں ساتھی ڈیمو استعمال کریں۔ یہ ایک چھوٹا ٹاسکس API ہے جو صفائی سے نمونوں کو ظاہر کرتا ہے۔ بعد میں، ہم دیکھیں گے کہ کس طرح ایک ہی خیال بڑے پیمانے پر ClipForge میں ظاہر ہوتا ہے۔

ذخیرہ کلون کریں اور انحصار انسٹال کریں۔

git clone https://github.com/otutukingsley/api-conf-demo.git
cd api-conf-demo
npm install

سرور شروع کریں:

npm run dev

آپ کو اپنا API دیکھنا چاہئے۔ http://localhost:8080انٹرایکٹو دستاویزات میں شامل ہیں: /reference.

اہم فولڈرز ہیں:

src/
├── db/schema/          # Persistence layer (Drizzle tables)
├── lib/schemas/        # HTTP contract layer (Zod + OpenAPI)
├── lib/errors/         # One error envelope for every failure
├── routes/tasks/       # Route contracts + handlers
├── services/           # Business logic and DB access
├── app.ts              # Middleware, routers, OpenAPI wiring
└── env.ts              # Zod-validated environment config

یہ اب بھی MVC ہے۔ اوزار بہتر ہیں۔

MVC طرز کی تہہ جس میں HTTP کلائنٹ، کنٹرولر، معاہدہ اسکیما، سروس ماڈل اور ڈیٹا بیس شامل ہے۔

شکل 4: یہ اب بھی MVC ہے۔ روٹس اور ہینڈلرز بطور کنٹرولر، سکیما بطور کنٹریکٹ، خدمات بطور ماڈل۔

  • ماڈل اپنی کاروباری منطق اور ڈیٹا بیس تک رسائی کی خدمات فراہم کرتا ہے۔

  • انکوائری/معاہدہ اسکیماس اس بات کی وضاحت کرتے ہیں کہ ڈیٹا ڈی بی اور ایچ ٹی ٹی پی کی حدود میں کیسے نظر آتا ہے۔

  • کنٹرولر راستہ معاہدہ کا اعلان کرتا ہے اور ہینڈلر اسے پورا کرتا ہے۔

ڈیمو API کے ذریعے ایک درخواست اس طرح نظر آئے گی:

مڈل ویئر، روٹ کنٹریکٹس، ہینڈلرز، سروسز اور ڈیٹا بیس کے ذریعے پوسٹ/ٹاسک کی درخواست کی ترتیب کا خاکہ۔

شکل 5: ڈیمو API کے ذریعے بہاؤ کی درخواست کریں، بشمول توثیق کی ناکامی کا راستہ۔

اس خاکے میں، درخواستیں کلائنٹ، مڈل ویئر، روٹ کنٹریکٹ، ہینڈلر، سروس اور ڈی بی کے ذریعے بہہ جاتی ہیں۔ مڈل ویئر لاگنگ اور CORS کو ہینڈل کرتا ہے۔ راستے کا معاہدہ Zod کو اپنے جسم کی توثیق کرنے کے لیے استعمال کرتا ہے۔

اگر توثیق ناکام ہوجاتی ہے، کلائنٹ 422 ApiError ہینڈلر کو کبھی پھانسی نہیں دی جاتی ہے۔ پاس ہونے پر، ہینڈلر کو داخل کردہ پیغام موصول ہوتا ہے۔ CreateTaskInputفون کال TaskService.create()سروس قطار اور کلائنٹ کو داخل اور تجزیہ کرتی ہے۔ 201 کام کرنے والا JSON استعمال کریں۔

6. API اسکیما کی وضاحت کیسے کریں۔

HTTP معاہدوں کے ساتھ شروع کریں۔ src/lib/schemas/task.ts:

import { z } from '@hono/zod-openapi';

export const taskStatusSchema = z
  .enum(['todo', 'in_progress', 'done'])
  .openapi('TaskStatus');

export const taskSchema = z
  .object({
    id: z.string().uuid().openapi({
      example: '8e2c9f0a-2222-4a5a-9c3e-1a2b3c4d5e6f',
    }),
    title: z.string().min(1).max(120).openapi({
      example: 'Write the talk abstract',
    }),
    description: z.string().max(2000).nullable().openapi({
      example: 'Cover Hono + Zod patterns',
    }),
    status: taskStatusSchema.default('todo'),
    dueDate: z.string().date().nullable().openapi({
      example: '2026-07-15',
    }),
    createdAt: z.string().openapi({ example: '2026-06-28 10:15:00' }),
    updatedAt: z.string().openapi({ example: '2026-06-28 10:15:00' }),
  })
  .openapi('Task');

export type Task = z.infer;

وہ ایک آبجیکٹ اب رن ٹائم توثیق کرنے والا، ٹائپ اسکرپٹ کی قسم، اور ایک OpenAPI جزو ہے۔ Task.

درخواست اسکیموں کو دوبارہ اعلان کرنے کے بجائے اسی بنیاد سے اخذ کیا جانا چاہئے۔

export const createTaskSchema = taskSchema
  .pick({ title: true, description: true, status: true, dueDate: true })
  .partial({ description: true, status: true, dueDate: true })
  .openapi('CreateTask');

export const updateTaskSchema = createTaskSchema
  .partial()
  .openapi('UpdateTask');

export type CreateTaskInput = z.infer;
export type UpdateTaskInput = z.infer;

CreateTask کبھی شامل نہ کریں۔ idکیونکہ کلائنٹ پیغام نہیں بھیج رہا ہے۔ UpdateTask تمام فیلڈز کو اختیاری بنائیں۔ PATCH آپ کسی بھی ذیلی سیٹ کو چھو سکتے ہیں۔

createTaskSchema اور UpdateTaskSchema حاصل کرنے والا taskSchema

شکل 6: درخواست کا اسکیما اسی بنیادی وسائل کے اسکیما سے اخذ کیا گیا ہے۔

ہمارے پاس ایک ذریعہ کے ساتھ تین معاہدے ہیں۔

7. ڈیٹا بیس اسکیما کو API اسکیما سے الگ کرنے کا طریقہ

ڈیٹا بیس کی قطاروں اور API کے جوابات کو ایک ہی چیز کے طور پر ماننا پرکشش ہے۔ چھوٹے ڈیمو میں وہ اکثر ایک جیسے نظر آتے ہیں۔ پیداوار میں، وہ مختلف ہیں.

جان بوجھ کر انہیں الگ فائلوں میں رکھیں۔

ڈیٹا بیس پرسٹینس اسکیما کو سروس لیئر میں HTTP کنٹریکٹ اسکیما میں میپ کیا گیا ہے۔

شکل 7: ہم ڈیٹا بیس اسکیما اور HTTP اسکیما کو دو جان بوجھ کر پرتوں کے طور پر رکھتے ہیں۔

تصویر 7 سروس میں "نقشہ” کے لیبل والے تیر کے ذریعے جڑے ہوئے دو پینل دکھاتا ہے۔

بائیں پین src/db/schema/ (استقامت):

  • بوندا باندی ٹیبل کی تعریف کے ساتھ شروع کریں۔

  • وہ میزیں ایس کیو ایل کی منتقلی کو چلاتی ہیں۔

  • یہ قطاروں کو پارس کرنے کے لیے بوندا باندی-زوڈ اسکیما بھی بناتا ہے۔

  • اور ڈیٹا بیس باؤنڈری میں ٹائپ اسکرپٹ کے لیے قطار اندراج/سلیکشن کی قسم

دائیں پینل src/lib/schemas/ (HTTP معاہدہ):

  • میں Zod+ استعمال کرتا ہوں۔ .openapi() عوامی معاہدے کے ساتھ

  • درخواست کے باڈی/پیرامیٹر کی وضاحت کرتا ہے جو کلائنٹ بھیج سکتا ہے۔

  • جوابی جسم کی وضاحت کرتا ہے جو کلائنٹ کو موصول ہوتا ہے۔

  • OpenAPI اجزاء کو رجسٹر کرتا ہے جو استعمال کرتے ہیں۔ /doc اور /reference

درمیانی تیر اہم ہے۔ ڈیٹا بیس فارم خود بخود API فارم نہیں ہے۔ کہ سروس ان کے درمیان نقشہ۔ یہی وجہ ہے کہ اندرونی کالم بائیں جانب دائیں ہاتھ کے HTTP معاہدے کا حصہ بنے بغیر موجود ہوسکتے ہیں۔

سیدھے الفاظ میں:

ڈیمو سے بوندا باندی کی میز یہ ہے:

import { sql } from 'drizzle-orm';
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
import { z } from 'zod';

export const taskStatusValues = ['todo', 'in_progress', 'done'] as const;

export const tasks = sqliteTable('tasks', {
  id: text('id').primaryKey(),
  title: text('title').notNull(),
  description: text('description'),
  status: text('status', { enum: taskStatusValues }).notNull().default('todo'),
  dueDate: text('due_date'),
  createdAt: text('created_at')
    .notNull()
    .default(sql`(current_timestamp)`),
  updatedAt: text('updated_at')
    .notNull()
    .default(sql`(current_timestamp)`),
});

export const selectTaskSchema = createSelectSchema(tasks, {
  status: z.enum(taskStatusValues),
});

export const insertTaskSchema = createInsertSchema(tasks, {
  id: () => z.string().uuid().optional(),
  title: () => z.string().min(1).max(120),
  description: () => z.string().max(2000).nullable().optional(),
  status: z.enum(taskStatusValues).optional(),
  dueDate: () => z.string().date().nullable().optional(),
});

ایک جدول کی تعریف تین نتائج فراہم کرتی ہے:

  1. ٹائپ اسکرپٹ کی اقسام کالم سے اندازہ لگایا گیا ہے۔

  2. ایس کیو ایل مائیگریشن کی طرف سے پیدا کیا drizzle-kit

  3. زوڈ اسکیما کے ذریعے drizzle-zod

یہ ڈیٹا بیس کی حدود میں مفید ہے۔ یہ HTTP اسکیما کو تبدیل نہیں کرتا ہے۔

جس لمحے آپ داخلہ شامل کرتے ہیں۔ archivedAt API کے ذریعے واپس کیے گئے کالموں یا کیلکولیٹڈ فیلڈز (اگر کالم نہیں ہیں) کے اخراجات قسطوں میں ادا کیے جاتے ہیں۔ انحراف ایک تکلیف دہ ری فیکٹرنگ کے بجائے ایک عام تبدیلی بن جاتا ہے۔

ایک ایسی خدمت جو ڈیٹا بیس کی قطاروں کو اندرونی فیلڈز کے ساتھ عوامی API کے جوابات میں نقش کرتی ہے۔

شکل 8: خدمات اس بات کا تعین کرتی ہیں کہ کلائنٹ کیا دیکھ سکتے ہیں۔

8. ایک معاہدے کے طور پر ایک راستے کی وضاحت کیسے کریں

اس فن تعمیر میں، راستے "صرف درخواستوں کو ہینڈل” نہیں کرتے ہیں۔ ایک راستہ ایک معاہدے کا اعلان کرتا ہے: طریقہ، راستہ، درخواست سکیما، اور جوابی اسکیما۔

ایسے راستے کی وضاحت کریں جو ہینڈلر کے ذریعے پورا کیے گئے API معاہدے کے طور پر کام کرے۔

شکل 9: راستہ ایک معاہدے کا اعلان کرتا ہے۔ ہینڈلر اس کو پورا کرتا ہے۔

import { createRoute, z } from '@hono/zod-openapi';
import * as HttpStatusCodes from '@/lib/http-status-codes';
import { jsonContent } from '@/lib/openapi/json-content';
import { jsonApiErrorContent } from '@/lib/openapi/error-schema';
import {
  createTaskSchema,
  taskParamsSchema,
  taskSchema,
} from '@/lib/schemas/task';

export const createTask = createRoute({
  tags: ['Tasks'],
  method: 'post',
  path: '/tasks',
  summary: 'Create a task',
  request: {
    body: jsonContent(createTaskSchema, 'The task to create'),
  },
  responses: {
    [HttpStatusCodes.CREATED]: jsonContent(taskSchema, 'The created task'),
    [HttpStatusCodes.UNPROCESSABLE_ENTITY]:
      jsonApiErrorContent('Validation error'),
    [HttpStatusCodes.INTERNAL_SERVER_ERROR]: jsonApiErrorContent(
      'Internal server error',
    ),
  },
});

export const getTask = createRoute({
  tags: ['Tasks'],
  method: 'get',
  path: '/tasks/{id}',
  summary: 'Get a task by ID',
  request: {
    params: taskParamsSchema,
  },
  responses: {
    [HttpStatusCodes.OK]: jsonContent(taskSchema, 'The requested task'),
    [HttpStatusCodes.NOT_FOUND]: jsonApiErrorContent('Task not found'),
    [HttpStatusCodes.UNPROCESSABLE_ENTITY]:
      jsonApiErrorContent('Validation error'),
    [HttpStatusCodes.INTERNAL_SERVER_ERROR]: jsonApiErrorContent(
      'Internal server error',
    ),
  },
});

ایک چھوٹا مددگار جوابی بوائلر پلیٹ کو پڑھنے کے قابل رکھتا ہے۔

export function jsonContent(
  schema: T,
  description: string,
) {
  return {
    content: {
      'application/json': { schema },
    },
    description,
  };
}

نامزد ریاستی مستقل جادوئی اعداد کی جگہ لے لیتے ہیں۔ راستے میں آبجیکٹ کلید اور ہینڈلر میں سوئچ کیس کی طرح ایک ہی مستقل استعمال کریں۔ یہ اسٹیٹس کوڈز کو قابل تلاش اور مستقل بناتا ہے۔

9. اپنے ہینڈلرز کو پتلا کیسے رکھیں

ایک بار جب روٹ ایک معاہدے کی وضاحت کرتا ہے، ہینڈلر کو صرف معاہدے کو پورا کرنے کی ضرورت ہوتی ہے۔

export const getTask: AppRouteHandler = (c) => {
  try {
    const { id } = c.req.valid('param');
    return c.json(TaskService.get(id), HttpStatusCodes.OK);
  } catch (error) {
    const apiError = ApiError.parse(error);
    switch (apiError.statusCode) {
      case HttpStatusCodes.NOT_FOUND:
      case HttpStatusCodes.UNPROCESSABLE_ENTITY:
        return c.json(apiError.toResponseBody(), apiError.statusCode);
      default:
        return c.json(
          apiError.toResponseBody(),
          HttpStatusCodes.INTERNAL_SERVER_ERROR,
        );
    }
  }
};

export const createTask: AppRouteHandler = (c) => {
  try {
    const body = c.req.valid('json');
    return c.json(TaskService.create(body), HttpStatusCodes.CREATED);
  } catch (error) {
    const apiError = ApiError.parse(error);
    switch (apiError.statusCode) {
      case HttpStatusCodes.UNPROCESSABLE_ENTITY:
        return c.json(apiError.toResponseBody(), apiError.statusCode);
      default:
        return c.json(
          apiError.toResponseBody(),
          HttpStatusCodes.INTERNAL_SERVER_ERROR,
        );
    }
  }
};

کیا نوٹس ~ نہیں ہینڈلر میں:

c.req.valid('param') اور c.req.valid('json') یہ پہلے ہی تصدیق شدہ اور درج ہو چکا ہے۔ سروسز ڈیٹا بیس کی کارروائیوں کے مالک ہیں۔

get(id: string): Task {
  try {
    const row = db.select().from(tasks).where(eq(tasks.id, id)).get();
    if (!row) throw new NotFoundError(`Task ${id} not found`);
    return selectTaskSchema.parse(row);
  } catch (error) {
    throw ApiError.parse(error);
  }
},

خدمات جسم کو جوڑتی ہیں۔ try/catch ہم تمام ناکامیوں کو معمول کے مطابق بناتے ہیں: ApiError.parse(). Zod کی غلطیاں، اپنی مرضی کے مطابق ڈومین کی غلطیاں اور ڈرائیور کی غیر متوقع غلطیاں سب ایک ہی شکل میں آتی ہیں۔

10. ہر جگہ ایک غلطی کی شکل کیسے واپس کی جائے۔

کلائنٹس کو یہ نہیں سمجھنا چاہیے کہ غلطی یہ ہے: { message }, { error }یا خام اسٹیک ٹریس.

ڈیمو میں، ہر ناکامی ایک لفافہ بن جاتی ہے۔

ApiError.parse کے ذریعے ایک سے زیادہ خرابی کے ذرائع کو ایک JSON لفافے میں معمول بنایا گیا۔

شکل 10: ناکامی کے تمام راستے ناکامی کی ایک پیشین گوئی کی شکل میں نتیجہ اخذ کرتے ہیں۔

وہ فیکٹریاں جو راؤٹرز بناتی ہیں اپنے طرز عمل کو اس میں بناتی ہیں: defaultHook:

export function createRouter() {
  return new OpenAPIHono({
    defaultHook: (result, c) => {
      if (!result.success) {
        const apiError = ApiError.parse(result.error);
        return c.json(apiError.toResponseBody(), apiError.statusCode);
      }
    },
  });
}

تمام راؤٹرز اس فیکٹری سے گزرتے ہیں۔ پاتھ پیرامیٹر میں ایک غلط UUID، POST باڈی میں ایک گم شدہ فیلڈ، یا استفسار کے اسٹرنگ میں غلط گنتی کے نتیجے میں ہینڈلر کے عمل درآمد سے پہلے ایک ہی جوابی شکل آئے گی۔

ApiError.parse() یہ پیٹرن کا دوسرا نصف ہے.

public static parse(error: unknown): ApiError {
  if (error instanceof ApiError) return error;

  if (error instanceof ZodError) {
    return new ApiError('Validation error', {
      statusCode: 422,
      errors: error.flatten().fieldErrors,
    });
  }

  return new ApiError('Internal server error', { statusCode: 500 });
}

ہینڈلر اب بھی واضح ہے۔ switch اسٹیٹس کوڈ میں۔ یہ جان بوجھ کر ہے۔ ہر راستہ دستاویز کرتا ہے کہ وہ کس حالت میں واپس آسکتا ہے۔ ایسے ہینڈلرز جنہیں کبھی رہا نہیں کیا جانا چاہیے۔ 403 نہیں ہے 403 یہ مشترکہ مددگار کے لیے ڈیفالٹ پر بیٹھا ہوا ہے۔

تکرار نقطہ ہے۔ واضح بٹ یہاں ہوشیار ہے.

11. ایک ناقابل فراموش دستاویز کیسے بنائیں

چونکہ اسکیموں کو روٹ کی تعریفوں سے جوڑا جاتا ہے، اس لیے OpenAPI ایک الگ آپریشن کے بجائے آپ کے کوڈ کا بائی پروڈکٹ بن جاتا ہے۔

Zod اسکیما روٹ کی تعریفوں، OpenAPI JSON، اور اسکیلر حوالہ UI کے ساتھ بہہ رہی ہے۔

شکل 11: دستاویزات اسی راستے کی تعریف سے تیار کی جاتی ہیں جیسے رن ٹائم کوڈ۔

export function configureOpenAPI(app: OpenAPIHono) {
  app.doc('/doc', {
    openapi: '3.0.0',
    info: {
      title: 'Bulletproof Tasks API',
      version: '1.0.0',
    },
  });

  app.get(
    '/reference',
    apiReference({
      spec: { url: '/doc' },
      theme: 'kepler',
      layout: 'modern',
      pageTitle: 'Bulletproof Tasks API',
    }),
  );
}

اگر آپ روٹس فائل میں اسکیما یا رسپانس کوڈز کو تبدیل کرتے ہیں، تو اس کے مطابق دستاویز کو اپ ڈیٹ کیا جائے گا۔ بھولنے کے لیے کوئی دوسرا دستاویزی مرحلہ نہیں ہے۔

12. اپنی ایپ کو پروڈکشن کے لیے کیسے تیار کریں۔

صرف درخواست کی حدود کی قسم کی حفاظت کافی نہیں ہے۔ ترتیب اور عمل لائف سائیکل ایک ہی اصولوں کی ضرورت ہے۔

بوٹ کے وقت ماحولیاتی متغیرات کی توثیق کریں۔

import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z
    .enum(['development', 'test', 'production'])
    .default('development'),
  LOG_LEVEL: z
    .enum(['silent', 'debug', 'info', 'warn', 'error', 'fatal'])
    .default('info'),
  PORT: z.coerce.number().default(8080),
  DATABASE_URL: z.string().default('tasks.db'),
});

export const env = envSchema.parse(process.env);

اگر مطلوبہ اقدار غائب یا خراب ہیں، تو یہ عمل فوری طور پر ایک واضح Zod غلطی کے ساتھ ختم ہو جائے گا۔ یہ معلوم کرنے سے بہت بہتر ہے۔ undefined تین درخواستیں تیار کی گئیں۔

ساختی لاگنگ کو ترجیح دیں۔

ڈیمو Pino کا استعمال کرتا ہے۔ hono-pino ایک لائن کنسول لاگر کے بجائے۔ پیداوار میں آپ کو JSON لاگز کی ضرورت ہے۔ ترقی کے دوران، آپ کچھ پڑھنے کے قابل چاہتے ہیں۔ ایک مڈل ویئر دونوں کام کرسکتا ہے اور ہر درخواست UUID پاس کرسکتی ہے۔

صاف اختتام

ڈاکر اور پروسیس مینیجر کے ذریعہ بھیجا گیا۔ SIGTERM عمل کو ختم کرنے سے پہلے. اس کا خیال رکھنا۔ HTTP سرور کو بند کریں، پھر ڈیٹا بیس ہینڈل کو بند کریں اور باہر نکلیں۔ بصورت دیگر، آپ کی SQLite فائلیں (یا Postgres کنکشن پول) گندی حالت میں رہ سکتی ہیں۔

اپنے ایپ کے رن ٹائم کو پورٹیبل رکھیں

Hono مکمل طور پر اپنے اندرونی ویب سٹینڈرڈ API پر انحصار کرتا ہے۔ app. اس کا مطلب ہے کہ نوڈ پر وہی ایپلی کیشن آبجیکٹ چل سکتی ہے۔

import { serve } from '@hono/node-server';
import { app } from '@/app';

serve({ fetch: app.fetch, port: env.PORT });

یا ایک کنارے رن ٹائم میں تقریبا کچھ بھی نہیں:

import { app } from '@/app';

export default app;

یہ کوئی آسان مثال نہیں ہے۔ یہ مکمل اڈاپٹر ہے۔

13. پروڈکشن ایپس میں یہ پیٹرن کیسے پھیلتے ہیں۔

Tasks API ایک بہترین تعلیمی جگہ ہے۔ پیداواری نظام زیادہ پیچیدہ ہے۔ اپ لوڈز، بیک گراؤنڈ ٹاسک، ایک سے زیادہ پیکجز، اور دیرپا ورک فلوز ہیں۔

ClipForge ایک پروڈکشن ایپ کی ایک مفید مثال ہے جہاں ایک ہی خیال پیمانے پر ظاہر ہوتا ہے۔ Node.js، Hono، Zod، Drizzle، BullMQ، اور Nuxt کے ساتھ ایک خود میزبان ویڈیو پروسیسنگ ٹول کٹ۔ اپنا ویڈیو اپ لوڈ کریں اور آپ کو ایک ٹرانسکرپٹ، سب ٹائٹلز، خلاصہ، باب مارکر، اور تھمب نیلز ملیں گے۔

فن تعمیر مندرجہ ذیل ہے:

apps/
├── api/        # Hono REST API
├── worker/     # BullMQ video processor
└── web/        # Nuxt frontend
packages/
├── shared/     # Zod schemas, constants, shared types
└── providers/  # OpenAI, Anthropic, Deepgram integrations

ویب، API، کارکنوں، مشترکہ اسکیما، پوسٹگریس، ریڈیس، اور AI فراہم کنندگان کے ساتھ کلپفورج فن تعمیر

شکل 12: ClipForge ویب، API، اور ورکر پیکجوں میں ایک ہی معاہدہ فرسٹ کور کو برقرار رکھتا ہے۔

ویڈیو آپریشن نئے رسپانس فارم ایجاد کیے بغیر آڈیو اپ لوڈ، توثیق، نکالنے، وغیرہ جیسے مراحل سے گزرتے ہیں۔

ClipForge ویڈیو کام کے مراحل، اپ لوڈ سے تکمیل تک، راستہ ناکام ہو گیا۔

شکل 13: ویڈیو ٹاسک ان پٹ کے مراحل سے گزرتا ہے بغیر کوئی نیا رسپانس فارم ایجاد کئے۔

اہم حصہ ویڈیو پائپ لائن نہیں ہے۔ اہم حصہ یہ ہے کہ API اب بھی اسی معاہدے کی ترجیحی اصولوں پر عمل کرتا ہے۔

پیکجوں کے درمیان مشترکہ Zod اسکیما

ClipForge HTTP معاہدوں کو برقرار رکھتا ہے۔ packages/sharedلہذا، APIs، کارکنان، اور ویب ایپس ایک الفاظ کا اشتراک کرتے ہیں۔

export const processingFeatureSchema = z.enum([
  'transcription',
  'subtitles',
  'summary',
  'chapters',
  'thumbnails',
]);

export const videoUploadSchema = z.object({
  title: z.string().min(3).max(200),
  description: z.string().max(2000).optional(),
  features: z
    .array(processingFeatureSchema)
    .default([
      'transcription',
      'subtitles',
      'summary',
      'chapters',
      'thumbnails',
    ]),
  priority: z.enum(['low', 'normal', 'high']).default('normal'),
});

export const jobStatusSchema = z.object({
  jobId: z.string(),
  title: z.string().optional(),
  state: jobStateSchema,
  stage: jobStageSchema,
  progress: z.number().min(0).max(100),
  result: jobResultSchema.optional(),
  failedReason: z.string().optional(),
  createdAt: z.string(),
  updatedAt: z.string(),
});

جب کارکن ایک قدم مکمل کرتے ہیں، تو وہ کوئی نیا جوابی فارم نہیں بناتے ہیں۔ اپ ڈیٹس اسی اسکیما کے خلاف بیان کرتی ہیں جو API کلائنٹ کو واپس کرتا ہے۔

راستہ اب بھی معاہدے کی وضاحت کرتا ہے۔

ClipForge کا اپ لوڈ پاتھ ورکنگ ڈیمو جیسا ہی ہے، جس میں ملٹی پارٹ فارم ڈیٹا اور ریٹ کی حدیں ہیں۔

export const uploadVideo = createRoute({
  tags: ['videos'],
  method: 'post',
  path: '/videos/upload',
  middleware: [
    sessionMiddleware,
    apiKeysMiddleware,
    rateLimitMiddleware({
      windowMs: 60_000,
      max: 10,
      keyPrefix: 'ratelimit:upload',
    }),
  ] as const,
  request: {
    body: {
      content: {
        'multipart/form-data': {
          schema: z.object({
            file: z.instanceof(File),
            title: z.string().min(3).max(200),
            description: z.string().max(2000).optional(),
            features: z.string().optional(),
            priority: z.enum(['low', 'normal', 'high']).optional(),
          }),
        },
      },
    },
  },
  responses: {
    [HTTP_STATUS.ACCEPTED]: jsonContent(
      jobStatusSchema,
      'Video accepted for processing',
    ),
    [HTTP_STATUS.BAD_REQUEST]: jsonApiErrorContent('Invalid request'),
    [HTTP_STATUS.UNPROCESSABLE]: jsonApiErrorContent('Validation error'),
    [HTTP_STATUS.TOO_MANY_REQUESTS]: jsonApiErrorContent('Rate limit exceeded'),
  },
});

ہینڈلر ان پٹ کی توثیق کرتا ہے، ایک ٹاسک قطار لکھتا ہے، قطار میں BullMQ ٹاسک شامل کرتا ہے، اور واپس آتا ہے۔ 202 Accepted ملازمت کی حیثیت درج ہے۔ بھاری اشیاء اٹھانا کارکنوں کے ساتھ ہوتا ہے۔ API ایک معاہدہ کی پرت بنی ہوئی ہے۔

ClipForge ویڈیو اپ لوڈ کرنے، قطار لگانے، اور جاب اسٹیٹس پولنگ کا تسلسل کا خاکہ۔

شکل 14: جب کارکن ہیوی لفٹنگ کرتا ہے، API اپ لوڈ کو قبول کرتا ہے اور ان پٹ ٹاسک کی حیثیت واپس کرتا ہے۔

شکل 14 پانچ شرکاء کے ساتھ ایک ترتیب کا خاکہ ہے: Web UI، Hono API، BullMQ، Worker، اور Postgres۔ ‘اپ لوڈ قبول کریں’ اور ‘مکمل پروسیسنگ’ کے درمیان ایک متضاد حد دکھاتا ہے۔ خاکہ میں تمام مراحل درج ذیل ہیں:

  1. ویب UI منتقل کرتا ہے۔ POST /videos/upload Hono API کو۔

  2. Hono API چلتا ہے۔ Zod + middleware (ان پٹ، سیشن، شرح محدود کرنے، اور متعلقہ چیک کی توثیق کرتا ہے۔)

  3. Hono API مندرجہ ذیل کام کرتا ہے: insert job row پوسٹگریس میں، ایک کام شروع ہونے سے پہلے موجود ہوتا ہے۔

  4. Hono API بھیجتا ہے: enqueue process-video BullMQ پر۔

  5. Hono API فوری طور پر واپس آتا ہے۔ 202 + jobStatusSchema ویب UI پر۔ اس مقام پر، کلائنٹ نے کام کی حیثیت میں داخل کیا ہے، لیکن نقل ابھی مکمل نہیں ہوئی ہے۔

  6. BullMQ بعد میں آتا ہے۔ process job کارکنوں کو۔

  7. کارکن دوڑتا ہے۔ transcribe / analyze / thumbnails پس منظر کے کام کے طور پر۔

  8. کارکن لکھتے ہیں update stage + result قدم مکمل ہونے کے بعد، پوسٹگریس پر واپس جائیں۔

  9. ویب UI پولز کا استعمال کرتے ہوئے: GET /videos/jobs/{id} میں Hono API کے خلاف ہوں۔

  10. Hono API پوسٹگریس سے تازہ ترین کارروائیوں کو پڑھتا اور واپس کرتا ہے۔ jobStatusSchema دوبارہ، وہی جوابی فارم جو مرحلہ 5 میں تھا اپ ڈیٹ کر دیا گیا ہے۔ stage, progressاور آخر میں result.

لہذا، API ایک تیز معاہدے کی پرت کو برقرار رکھتا ہے: قبول کریں، برقرار رہیں، قطار میں رہیں، اور جواب دیں۔ مزدور سست پائپ لائن کے مالک ہیں۔ UI ایک مشترکہ ریاستی اسکیمے کی پولنگ کے ذریعے پیشرفت کو ٹریک کرتا ہے۔

ڈیٹا بیس اسکیما اب بھی الگ ہے۔

ClipForge پوسٹگریس میں اپنے کام کو ذخیرہ کرنے کے لیے بوندا باندی کا استعمال کرتا ہے۔

export const jobs = pgTable('jobs', {
  id: varchar('id', { length: 36 }).primaryKey(),
  sessionId: varchar('session_id', { length: 36 }).notNull(),
  state: jobStateEnum('state').notNull().default('waiting'),
  stage: jobStageEnum('stage').notNull().default('uploading'),
  progress: integer('progress').notNull().default(0),
  title: varchar('title', { length: 200 }).notNull(),
  features: jsonb('features').$type().notNull(),
  provider: varchar('provider', { length: 50 }).notNull().default('openai'),
  result: jsonb('result').$type(),
  failedReason: text('failed_reason'),
  createdAt: timestamp('created_at', { withTimezone: true })
    .notNull()
    .defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true })
    .notNull()
    .defaultNow(),
});

جدولوں میں درج ذیل مستقل مسائل ہیں: filePath اور sessionId. عوام jobStatusSchema آپ کو ہر چیز کو بے نقاب کرنے کی ضرورت نہیں ہے۔ یہ وہی ڈی بی بمقابلہ API تقسیم ہے جو ورکنگ ڈیمو میں ایک حقیقی ورک فلو پر لاگو ہوتا ہے۔

وہی پیداواری عادات اب بھی لاگو ہوتی ہیں۔

ClipForge بوٹ کے وقت Zod کے ساتھ ماحولیاتی متغیرات کی توثیق کرتا ہے اور OpenAPI اور Scalar کو ترتیب دیتا ہے۔ /doc اور /referenceغلطی کو معمول کے مطابق بنائیں: ApiErrorقطاروں اور ریڈیس کو بند کریں۔ SIGTERM.

سبق آسان ہے۔ اگر آپ کی چھوٹی ایپ کو صحیح طریقے سے تشکیل دیا گیا ہے تو، آپ کی بڑی ایپ کو کسی مختلف فلسفے کی ضرورت نہیں ہے۔ اس کے لیے ایک ہی کنٹریکٹ فرسٹ کور کے اوپر مزید پیکجز، زیادہ مڈل ویئر، اور طویل عرصے سے چلنے والے کاموں کی ضرورت ہے۔

نتیجہ

ایک قسم سے محفوظ API مزید TypeScript شامل کرنے کے بارے میں نہیں ہے۔ خیال یہ ہے کہ معلومات کے بے کار ذرائع کو ختم کیا جائے۔

Hono اور Zod کے ساتھ آپ یہ کر سکتے ہیں:

  • رن ٹائم پر توثیق کی درخواست کریں۔

  • خودکار طور پر اندازہ ٹائپ کریں۔

  • اسی راستے کی تعریف سے OpenAPI دستاویز بنائیں

  • ڈیٹا بیس اسکیما اور HTTP اسکیما کو جان بوجھ کر الگ رکھا گیا ہے۔

  • ہر ناکامی کا راستہ ایک متوقع غلطی کی شکل لوٹاتا ہے۔

اگر آپ ان نمونوں کا سب سے چھوٹا پڑھنے کے قابل ورژن چاہتے ہیں تو، api-conf-demo سے شروع کریں۔ پھر ClipForge پر ایک نظر ڈالیں اور دیکھیں کہ جب API اپ لوڈز، قطاروں، اور ملٹی سٹیپ پروسیسنگ کے سامنے ہوتا ہے تو وہی خیال کیسے درست ہوتا ہے۔

ایک بار جب روٹ ڈیفینیشن معاہدہ بن جاتا ہے، تو دستاویزات بہنا بند ہو جاتی ہیں، ہینڈلرز پتلے ہو جاتے ہیں، اور کلائنٹس کو ایک قابل اعتماد API مل جاتا ہے۔

اوپر تک سکرول کریں۔