ترکیب Express و Typescript: نوشتن کنترلرها

Combining Typescript and Express: Controllers

01 شهریور 1399
ترکیب Express و Typescript: نوشتن کنترلر ها – قسمت 77

در قسمت قبل شروع به نوشتن کنترلرها کردیم و به کد زیر رسیدیم:

import { RequestHandler } from 'express';

export const createTodo: RequestHandler = (req, res, next) => {

}

به شما توضیح دادم که به جای تعیین تایپ تک تک پارامترها می توانیم تایپ کلی تابع نوشته شده را تعیین کنیم و همین کار را در کد بالا انجام داده ایم. نکته مهمی که باید به یاد داشته باشیم این است که دستور import بالا برای RequestHandler در هنگام کامپایل شدن به جاوا اسکریپت از بین می رود چرا که از پکیج types/express است و از ویژگی های خاص تایپ اسکریپت به شمار می رود. به عنوان قانون کلی به یاد داشته باشید که اگر کدهای شما در تایپ اسکریپت برای اجرا شدن برنامه ضروری نیستند، به احتمال زیاد در هنگام کامپایل شدن از بین می روند.

در مرحله بعد باید یک آرایه todo ایجاد کنیم:

import { RequestHandler } from 'express';

const TODOS = [];

export const createTodo: RequestHandler = (req, res, next) => {

}

این آرایه قرار است todo های جدید را در خود داشته باشد و طبیعتا در مموری نگهداری می شود بنابراین هر بار که سرور ما ریستارت شود، این آرایه نیز ریستارت شده و همه چیز از صفر شروع می شود. برای اینکه کار ما برای تعیین تایپ این آرایه راحت تر شود بهتر است یک مدل را برای آن تعریف کنیم. درون پوشه Src پوشه دیگری به نام models را ایجاد کنید که حاوی فایلی به نام todo.ts باشد. کدهای این فایل باید به شکل زیر باشد:

export class Todo {
  constructor(public id: string, public text: string) {}
}

در کد بالا از روش خلاصه تایپ اسکریپت برای تعریف خصوصیات استفاده کرده ام. اگر یادتان باشد برای این کار باید access modifier مورد نظر (مثلا public یا private) را به همراه نام خصوصیت به constructor پاس می دادیم. حالا هر زمان که از این کلاس نمونه ای بسازیم، این خصوصیات برای آن تعریف می شوند. اگر یادتان باشد در فصل های اول این دوره توضیح دادیم که هر کلاسی به صورت خودکار به عنوان یک تایپ عمل می کند و ما می توانیم از کلاس ها به عنوان تایپ های شخصی خودمان استفاده کنیم. اگر مقالات مربوط به آن را مطالعه نکرده اید، پیشنهاد می کنم حتما به قسمت های اولیه برگردید و آن ها را بخوانید.

حالا به فایل todo.ts در پوشه controllers برگشته و کلاس Todo را وارد آن کنید:

import { RequestHandler } from 'express';

import { Todo } from '../models/todo';

سپس از این کلاس برای تعیین تایپ آرایه TODOS استفاده می کنیم:

const TODOS: Todo[] = [];

سوال اینجاست که برای تابع createTodo چه کار کنیم؟ کار این تابع ساخت یک todo است بنابراین باید همین کار را انجام بدهیم. در ابتدا از کلاس Todo استفاده می کنم. Constructor کلاس Todo از من دو مقدار می خواهد: id و text (متن todo). می توانیم برای id از تابع math.random استفاده کنیم اما برای text چطور؟ متن todo ها قرار است از سمت کاربر بیاید بنابراین در بدنه درخواست ما وجود خواهد داشت. با این حساب می توان گفت:

const TODOS: Todo[] = [];

export const createTodo: RequestHandler = (req, res, next) => {
    const text = req.body.text;
    const newTodo = new Todo(Math.random().toString(), text);
}

یعنی در ابتدا text را از بدنه درخواست خودم می گیرم و سپس آن را به constructor کلاس Todo پاس می دهم. مشکل کد بالا این است که تایپ اسکریپت نمی تواند رشته بودن text دریافتی از بدنه درخواست را تشخیص بدهد و تایپ آن را any گذاشته است. چرا؟ به دلیل اینکه ما در هیچ قسمتی از کدهایمان توضیح نداده ایم که درخواست های دریافتی چه ساختاری دارند. یکی از راه حل های ممکن استفاده از type casting است که قبلا یاد گرفته بودیم. اگر شما از تایپ چیزی مطمئن هستید اما تایپ اسکریپت آن را نمی داند type casting راه حل بسیار خوبی است:

const TODOS: Todo[] = [];

export const createTodo: RequestHandler = (req, res, next) => {
    const text = (req.body as {text: string}).text;
    const newTodo = new Todo(Math.random().toString(), text);
}

یعنی req.body یک شیء خواهد بود که خصوصیتی به نام text (از نوع string) خواهد داشت. با این کار مطمئن می شویم که تایپ اسکریپت از همه چیز خبر دارد. در مرحله بعد باید این todo تازه ساخته شده را به آرایه TODOS بفرستیم:

const TODOS: Todo[] = [];

export const createTodo: RequestHandler = (req, res, next) => {
    const text = (req.body as { text: string }).text;
    const newTodo = new Todo(Math.random().toString(), text);
    TODOS.push(newTodo);
}

در ظاهر کدهای ما تکمیل شده است اما فعلا راهی برای فهمیدن این موضوع نداریم. یعنی اگر کاربر todo را ثبت کند، نمی فهمد که عملیات ثبت با موفقیت انجام شده است و هیچ feedback ای دریافت نمی کند. برای حل این مشکل نیز می توانیم یک response را برگردانیم:

import { RequestHandler } from 'express';

import { Todo } from '../models/todo';

const TODOS: Todo[] = [];

export const createTodo: RequestHandler = (req, res, next) => {
  const text = (req.body as {text: string}).text;
  const newTodo = new Todo(Math.random().toString(), text);

  TODOS.push(newTodo);

  res.status(201).json({message: 'Created the todo.', createdTodo: newTodo});
};

من status code برابر با 201 را به کاربر پاس می دهم که یعنی عملیات موفقیت آمیز بوده است. همچنین یک شیء json را به همراه این Status code ارسال خواهیم کرد که دارای خصوصیت message (با مقدار created the todo یا «todo ساخته شد») و خصوصیت createdTodo (با مقدار todo ساخته شده) است.

در مرحله بعد باید مطمئن شویم که body در درخواست ما وجود دارد تا تابع createTodo بتواند از آن برای ساخت todo ها استفاده کند بنابراین باید درخواست ها را در فایل app.ts تجزیه یا parse کنیم. یکی از راه های آسان برای این کار وارد کردن متد json از پکیج body-parser است:

import express, { Request, Response, NextFunction } from 'express';
import { json } from 'body-parser';

حالا می توانیم این متد را در همان فایل به عنوان یک middleware اجرا کنیم:

import express, { Request, Response, NextFunction } from 'express';
import { json } from 'body-parser';

import todoRoutes from './routes/todos';

const app = express();

app.use(json());

app.use('/todos', todoRoutes);

سپس به todos.ts در پوشه route می رویم و این کنترلر را وارد آن می کنیم:

import { Router } from 'express';

import { createTodo } from '../controllers/todos';

حالا می توانیم تابع خودمان را به مسیر post بدهیم:

import { Router } from 'express';

import { createTodo } from '../controllers/todos';

const router = Router();

router.post('/', createTodo);
// بقیه کدها //

قسمت post برنامه ما تکمیل شده است! از آنجایی که این برنامه یک REST API است نمی توانیم با مرورگر درخواست های post را ارسال کنیم بنابراین از نرم افزاری رایگان به نام postman استفاده می کنیم:

https://www.postman.com/

در این برنامه باید ابتدا نوع درخواست را مشخص کنید که من post را انتخاب می کنم. سپس url خود را در قسمت URL وارد کنید که برای برنامه ما http://localhost:3000/todos/ است (ما خودمان انتخاب کردیم که برنامه ما روی پورت 3000 اجرا شود). چرا todos/ به آدرس اضافه شده است؟ ما خودمان این موضوع را در فایل app.ts کدنویسی کرده ایم:

app.use('/todos', todoRoutes);

سپس در postman سربرگ body را انتخاب کنید چرا که می خواهیم یک body را به درخواست خودمان اضافه کنیم. در این سربرگ گزینه raw را انتخاب کرده و آن را از Text روی json تنظیم کنید. در نهایت روی دکمه Send کلیک کنید تا پاسخ را در postman مشاهده کنید:

ارسال درخواست با postman به back-end
ارسال درخواست با postman به back-end

به همین راحتی کار ما در قسمت post تمام شده است!

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جامع تایپ اسکریپت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مقالات مرتبط
ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو