برنامه نویسی

ایجاد ExpressJS خود از ابتدا (قسمت 1) – مبانی، روش ها و مسیریابی

سلام، آیا تا به حال به ایجاد چارچوب وب خود با استفاده از NodeJS فکر کرده اید تا بفهمید که در زیر هود چه اتفاقی می افتد، روش ها، میان افزارها، کنترلرها و غیره…

من می دانم که ما فریمورک های بسیار خوبی برای ایجاد برنامه های کاربردی عالی داریم، مانند ExpressJS، NestJS، Fastify، و غیره. اما می دانید که آنها چه کار می کنند، فکر می کنم اکثر توسعه دهندگان حتی به آن اهمیتی نمی دهند، فقط می خواهند ایجاد کنند. برنامه های آنها، و این مشکلی ندارد، اما ممکن است آنها تجربه بهتری در ایجاد برنامه ها و درک آنچه که فریم ورک انجام می دهد داشته باشند، اجازه می دهد قابلیت های فریم ورک را گسترش دهد، برخی از خطاهای غیرمنتظره را برطرف کند، مشکلات عملکرد را بهبود بخشد، به طور کلی زندگی آنها را آسان تر کند.

در طول مسیر ایجاد چارچوب وب، ما با چندین مفهوم مرتبط با جاوا اسکریپت، NodeJS و به طور کلی برنامه نویسی مانند الگوهای طراحی و عبارات منظم کار می کنیم.

کاربرد: امروز می خواهیم اولین قسمت از Web Framework خود را ایجاد کنیم تا بتوانیم مسیرها را ایجاد کنیم و این مسیرها را مدیریت کنیم.

الزامات:


برپایی

ما در اینجا می خواهیم یک راه اندازی اولیه ایجاد کنیم تا بتوانیم به کار ادامه دهیم.

mkdir web-framework

cd web-framework

npm init -y
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • ترمینال مورد نظر خود را باز کنید
  • یک پوشه با نام مورد نظر خود ایجاد کنید.
  • در پوشه وارد کنید
  • یک پروژه جدید npm را شروع کنید

واردات

src/app.js

const { createServer } = require('http')
const { match } = require('path-to-regexp')
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خط اول واردات createServer تابعی از Node.js داخلی http ماژول، که به ما اجازه می دهد یک سرور HTTP ایجاد کنیم. خط دوم تابع مطابقت را از path-to-regexp بسته، که به ما امکان می دهد URL ها را با مسیرهای خاص مطابقت دهیم.

برای نصب path-to-regex بسته:

npm install path-to-regex
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


کاربرد

src/app.js

const App = () => {
    const routes = new Map()
    const createMyServer = () => createServer(serverHandler.bind(this))
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را App تابع نقطه ورود اصلی برای چارچوب برنامه وب ما است. جدید ایجاد می کند Map شی به نام routes برای ذخیره تمام مسیرهای تعریف شده ما. همچنین یک تابع کمکی به نام تعریف می کند createMyServer که یک سرور HTTP با استفاده از serverHandler تابع، تابعی که به زودی ایجاد خواهیم کرد، کنترل کننده اصلی خواهد بود.


تعریف مسیر

قصد من این نیست که همه روش های HTTP را پوشش دهم، بلکه فقط برخی از آنها را به عنوان مثال نشان می دهم.

src/app.js

    const get = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/GET`) || []
        routes.set(`${path}/GET`, [...currentHandlers, ...handlers])
    }

    const post = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/POST`) || []
        routes.set(`${path}/POST`, [...currentHandlers, ...handlers])
    }

    const put = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/PUT`) || []
        routes.set(`${path}/PUT`, [...currentHandlers, ...handlers])
    }

    const patch = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/PATCH`) || []
        routes.set(`${path}/PATCH`, [...currentHandlers, ...handlers])
    }

    const del = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/DELETE`) || []
        routes.set(`${path}/DELETE`, [...currentHandlers, ...handlers])
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اینها توابع تعریف مسیر هستند که یک مسیر جدید را تعریف می کنند و کنترل کننده ها را برای هر روش HTTP (GET، POST، PUT، PATCH، DELETE) مشخص می کنند. الف می گیرند path آرگومان و هر تعداد از handlers. هر تابع کنترل کننده های فعلی را برای متد و مسیر مشخص شده از تابع بازیابی می کند routes شیء، هر کنترل کننده جدیدی را اضافه می کند و کنترل کننده های به روز شده را مجدداً در قسمت تنظیم می کند routes هدف – شی.

الگوی مطابق با مسیر و یافتن کنترل کننده مناسب باید شامل مسیر و در پایان نام متد باشد. مانند نمونه های زیر:

برای /test/1/GET مسیر است /test/1 و روش این است GET

برای /test1/2/test2/POST مسیر است /test1/2/test2 و روش این است POST

را Map راه خوبی برای ذخیره سازی هندلرها است زیرا در اینجا می توانیم از برخی توابع مفید مانند استفاده کنیم get و keys.


درمان URL ها

برای اینکه سیستم مسیریابی ما به خوبی کار می کند، باید با URL ها رفتار کنیم، زیرا آنها آنطور که ما انتظار داریم نمی آیند.

src/app.js

    const sanitizeUrl = (url, method) => {
        // Get the last part of the URL, removing the domain
        const urlParams = url.split('/').slice(1)

        // Remove querystrings from the last parameter
        const [lastParam] = urlParams[urlParams.length - 1].split('?')
        urlParams.splice(urlParams.length - 1, 1)

        // Create the URL with our pattern
        const allParams = [...urlParams, lastParam].join('/')
        const sanitizedUrl = `/${allParams}/${method.toUpperCase()}`

        return sanitizedUrl
    }

    const matchUrl = (sanitizedUrl) => {
        for (const path of routes.keys()) {
            const urlMatch = match(path, {
                decode: decodeURIComponent,
            })

            const found = urlMatch(sanitizedUrl)

            if (found) {
                return path
            }
        }
        return false
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اینها توابع کمکی هستند که برای پاکسازی و تطبیق URLها با مسیرهای مربوطه استفاده می شوند. sanitizeUrl یک URL و یک روش HTTP را می گیرد و هر رشته پرس و جو را حذف می کند، سپس پارامترها را به یک الگوی مطابق با ساختار مسیر متصل می کند. matchUrl از طریق مسیرها بررسی می کند که آیا URL با یکی از مسیرهای فعلی ما مطابقت دارد یا خیر.


مدیریت سرور

src/app.js

    const serverHandler = async (request, response) => {
        const sanitizedUrl = sanitizeUrl(request.url, request.method)

        const match = matchUrl(sanitizedUrl)

        if (match) {
            const middlewaresAndControllers = routes.get(match)
            console.log(middlewaresAndControllers)
            response.statusCode = 200
            response.end('Found')
        } else {
            response.statusCode = 404
            response.end('Not found')
        }
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ابتدا تابع تماس می گیرد sanitizeUrl() با request.url و request.method به عنوان آرگومان هایی برای ایجاد یک URL سالم، که برای بررسی مسیرهای تنظیم شده در چارچوب استفاده می شود.

سپس تابع فراخوانی می کند matchUrl() با URL پاک‌سازی‌شده برای یافتن مسیری منطبق که در چارچوب تنظیم شده است. اگر مطابقت پیدا شود، لیست میان افزارها و کنترلرهای مرتبط با آن مسیر را از نقشه مسیرها بازیابی می کند.

اگر میان‌افزارها و/یا کنترل‌کننده‌های مرتبط با مسیر وجود داشته باشد، تابع آن‌ها را در کنسول ثبت می‌کند و کد وضعیت پاسخ را روی 200 تنظیم می‌کند که نشان می‌دهد درخواست موفقیت‌آمیز بوده است. در نهایت، پاسخ را با پیام به پایان می رساند 'Found'.

اگر مطابقت پیدا نشد، تابع کد وضعیت پاسخ را روی 404 تنظیم می کند که نشان می دهد منبع درخواستی پیدا نشد. در نهایت، پاسخ را با پیام به پایان می رساند 'Not found'.


تابع Run and Exports

src/app.js

    const run = (port) => {
        const server = createMyServer()
        server.listen(port)
    }


    return {
        run,
        get,
        post,
        patch,
        put,
        del
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را run تابع مسئول راه اندازی سرور با فراخوانی روش گوش دادن نمونه سرور است. را listen متد یک شماره پورت را به عنوان آرگومان می پذیرد، که درگاهی است که سرور به درخواست های دریافتی گوش می دهد.

در این تابع، یک نمونه جدید از سرور با فراخوانی ایجاد می شود createMyServer تابعی که قبلا در کد تعریف شده است. سپس listen متد در نمونه سرور فراخوانی می شود و در آرگومان پورت ارائه شده به سرور ارسال می شود run تابع.

در نهایت، ما می توانیم تمام توابع عمومی را به عنوان یک صادر کنیم closure بازگرداندن آنها


کد کامل

src/app.js

const { createServer } = require('http')
const { match } = require('path-to-regexp')

const App = () => {
    const routes = new Map()
    const createMyServer = () => createServer(serverHandler.bind(this))


    const get = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/GET`) || []
        routes.set(`${path}/GET`, [...currentHandlers, ...handlers])
    }

    const post = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/POST`) || []
        routes.set(`${path}/POST`, [...currentHandlers, ...handlers])
    }

    const put = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/PUT`) || []
        routes.set(`${path}/PUT`, [...currentHandlers, ...handlers])
    }

    const patch = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/PATCH`) || []
        routes.set(`${path}/PATCH`, [...currentHandlers, ...handlers])
    }

    const del = (path, ...handlers) => {
        const currentHandlers = routes.get(`${path}/DELETE`) || []
        routes.set(`${path}/DELETE`, [...currentHandlers, ...handlers])
    }

    const sanitizeUrl = (url, method) => {
        const urlParams = url.split('/').slice(1)

        // remove querystrings from the last parameter
        const [lastParam] = urlParams[urlParams.length - 1].split('?')
        urlParams.splice(urlParams.length - 1, 1)

        // create the URL with our pattern
        const allParams = [...urlParams, lastParam].join('/')
        const sanitizedUrl = `/${allParams}/${method.toUpperCase()}`

        return sanitizedUrl
    }

    const matchUrl = (sanitizedUrl) => {
        for (const path of routes.keys()) {
            const urlMatch = match(path, {
                decode: decodeURIComponent,
            })

            const found = urlMatch(sanitizedUrl)

            if (found) {
                return path
            }
        }
        return false
    }


    const serverHandler = async (request, response) => {
        const sanitizedUrl = sanitizeUrl(request.url, request.method)

        const match = matchUrl(sanitizedUrl)

        if (match) {
            const middlewaresAndControllers = routes.get(match)
            console.log(middlewaresAndControllers)
            response.statusCode = 200
            response.end('Found')
        } else {
            response.statusCode = 404
            response.end('Not found')
        }
    }

    const run = (port) => {
        const server = createMyServer()
        server.listen(port)
    }


    return {
        run,
        get,
        post,
        patch,
        put,
        del
    }
}

module.exports = App
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


استفاده و تست

من یک فایل جداگانه ایجاد کردم به نام index.js بیرون از src پوشه برای تست چارچوب ما.

index.js

const App = require('./src/app')

const app = App()


app.get('/test/test2', function test() { }, function test2() { })
app.post('/test', (req, res) => console.log('test'))
app.patch('/test', (req, res) => console.log('test'))
app.put('/test', (req, res) => console.log('test'))
app.del('/test', (req, res) => console.log('test'))



const start = async () => {
    app.run(3000)
}

start()
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

می توانید این فایل را اجرا کرده و با استفاده از سرویس گیرنده HTTP مورد علاقه خود برنامه را آزمایش کنید.

node index.js
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


مراحل بعدی

در آموزش بعدی در حال کار با middlewares و controllers. اگر از این آموزش خوشتان آمد، در واکنش خود دریغ نکنید و من را دنبال کنید تا از ابتدا آموزش های بعدی من را ببینید. من سعی می کنم حداقل یک آموزش در هفته بنویسم.

تشکر به زودی میبینمت!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا