NestJS — Node.js للمؤسسات

رفع الملفات، البث، إرسال البريد، والتدويل (i18n)

16 دقيقة الدرس 47 من 48

رفع الملفات، البث، إرسال البريد، والتدويل (i18n)

تحتاج تطبيقات NestJS الإنتاجية حتمًا إلى قبول ملفات ثنائية، وبثّ حمولات ضخمة للعملاء، وإرسال رسائل بريد إلكتروني معاملاتية، وتقديم المحتوى بلغات متعددة. يغطّي هذا الدرس الجوانب الأربعة باستخدام آليات NestJS درجة أولى: اعتراضات Multer للرفع، وStreamableFile للبث، و@nestjs/mailer للبريد، وnestjs-i18n للتدويل.

رفع الملفات باستخدام FileInterceptor وMulter

يُغلّف NestJS مكتبة Multer عبر @nestjs/platform-express. استخدم FileInterceptor (ملف واحد) أو FilesInterceptor (ملفات متعددة) على مستوى المسار، ثم اقرأ الرفع عبر @UploadedFile():

import { Controller, Post, UseInterceptors, UploadedFile, BadRequestException, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname } from 'path'; import { Express } from 'express'; @Controller('uploads') export class UploadsController { @Post('avatar') @UseInterceptors( FileInterceptor('file', { storage: diskStorage({ destination: './uploads', filename: (_req, file, cb) => { const unique = Date.now() + '-' + Math.round(Math.random() * 1e9); cb(null, `${unique}${extname(file.originalname)}`); }, }), limits: { fileSize: 2 * 1024 * 1024 }, // 2 ميغابايت fileFilter: (_req, file, cb) => { const allowed = /\.(jpg|jpeg|png|webp)$/i; if (!allowed.test(file.originalname)) { return cb(new BadRequestException('Only image files are allowed'), false); } cb(null, true); }, }), ) uploadAvatar(@UploadedFile() file: Express.Multer.File) { if (!file) throw new BadRequestException('No file provided'); return { path: file.path, size: file.size }; } }
تحقّق من الامتداد ونوع MIME معًا. تُرسل file.mimetype من طرف العميل ويمكن تزويرها. في السياقات عالية الأمان، اقرأ أول بايتات (magic bytes) باستخدام مكتبة مثل file-type للتأكّد من الصيغة الفعلية.

بثّ الاستجابات باستخدام StreamableFile

عندما تحتاج إلى تمرير ملف أو بثّ Node.js Readable مرة أخرى للعميل دون تخزينه كاملًا في الذاكرة، أعِد StreamableFile. يضبط NestJS الترويسات الصحيحة ويمرّر البثّ:

import { Controller, Get, Param, Res, StreamableFile } from '@nestjs/common'; import { createReadStream } from 'fs'; import { join } from 'path'; import { Response } from 'express'; @Controller('files') export class FilesController { @Get(':filename') downloadFile( @Param('filename') filename: string, @Res({ passthrough: true }) res: Response, ): StreamableFile { const filePath = join(process.cwd(), 'uploads', filename); const stream = createReadStream(filePath); res.set({ 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="${filename}"`, }); return new StreamableFile(stream); } }
passthrough: true إلزامي. عند حقن @Res()، يُسلّم NestJS عادةً التحكّم الكامل لـ Express. يتيح ضبط passthrough: true تعيين الترويسات مع السماح لـ NestJS بإنهاء الاستجابة — بما فيها تمرير StreamableFile.

إرسال البريد الإلكتروني باستخدام @nestjs/mailer

ثبّت الحزمة واضبط وسيلة نقل (SMTP، SendGrid، إلخ). استخدم قوالب Handlebars المخزّنة في مجلد templates/ لرسائل HTML:

// app.module.ts (مقتطف) import { MailerModule } from '@nestjs-modules/mailer'; import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; import { join } from 'path'; MailerModule.forRoot({ transport: { host: process.env.MAIL_HOST, port: 587, auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASS }, }, defaults: { from: '"No Reply" <noreply@example.com>' }, template: { dir: join(__dirname, 'templates'), adapter: new HandlebarsAdapter(), options: { strict: true }, }, }), // mail.service.ts import { Injectable } from '@nestjs/common'; import { MailerService } from '@nestjs-modules/mailer'; @Injectable() export class MailService { constructor(private mailer: MailerService) {} async sendWelcome(to: string, name: string): Promise<void> { await this.mailer.sendMail({ to, subject: 'Welcome to Our Platform', template: 'welcome', // templates/welcome.hbs context: { name }, }); } }
لا تُضمّن بيانات الاعتماد في الكود أبدًا. اقرأ MAIL_HOST وMAIL_USER وMAIL_PASS دائمًا من متغيّرات البيئة (مثلًا عبر @nestjs/config). ارفع .env.example بقيم وهمية، ولا ترفع .env الحقيقي أبدًا.

التدويل باستخدام nestjs-i18n

توفّر nestjs-i18n مزخرفات وأنابيب وخدمة لتقديم النصوص المترجمة. خزّن الترجمات في ملفات JSON لكل لغة، ثم احقن I18nService أو استخدم المزخرف @I18nLang() لاكتشاف اللغة الحالية من ترويسة Accept-Language أو معامل الاستعلام أو الكوكي:

// i18n/en/common.json → { "WELCOME": "Welcome, {name}!" } // i18n/ar/common.json → { "WELCOME": "أهلاً، {name}!" } // app.module.ts (مقتطف) import { I18nModule, AcceptLanguageResolver } from 'nestjs-i18n'; import { join } from 'path'; I18nModule.forRoot({ fallbackLanguage: 'en', loaderOptions: { path: join(__dirname, '/i18n/'), watch: true }, resolvers: [AcceptLanguageResolver], }), // greet.service.ts import { Injectable } from '@nestjs/common'; import { I18nService } from 'nestjs-i18n'; @Injectable() export class GreetService { constructor(private i18n: I18nService) {} async greet(lang: string, name: string): Promise<string> { return this.i18n.translate('common.WELCOME', { lang, args: { name } }); } }

الخلاصة

يوفّر NestJS أوليّات نظيفة قائمة على المزخرفات للجوانب الأربعة. يُغلّف FileInterceptor مكتبة Multer للتعامل مع الرفع مع التحقّق من الحجم والنوع. يُمرّر StreamableFile بثوث Node.js للعملاء دون تخزين في الذاكرة. تدمج @nestjs-modules/mailer مع أي مزوّد SMTP باستخدام قوالب Handlebars. تُخرج nestjs-i18n جميع النصوص الموجّهة للمستخدم إلى ملفات JSON للغات، مُبقيةً وحدات التحكّم خالية من الرسائل المُضمّنة. يجعل الجمع بين هذه الأدوات واجهتك البرمجية جاهزة للإنتاج عبر الحدود اللغوية.