Panduan komprehensif untuk memahami dan mengimplementasikan middleware TypeScript di aplikasi Express.js. Jelajahi pola tipe lanjutan untuk kode yang kuat dan mudah dikelola.
Middleware TypeScript: Menguasai Pola Tipe Middleware Express
Express.js, kerangka kerja aplikasi web Node.js yang minimalis dan fleksibel, memungkinkan pengembang untuk membangun API dan aplikasi web yang kuat dan skalabel. TypeScript meningkatkan Express dengan menambahkan pengetikan statis, meningkatkan pemeliharaan kode, dan menangkap kesalahan lebih awal. Fungsi middleware adalah landasan Express, memungkinkan Anda untuk mencegat dan memproses permintaan sebelum mencapai penangan rute Anda. Artikel ini menjelajahi pola tipe TypeScript lanjutan untuk mendefinisikan dan menggunakan middleware Express, meningkatkan keamanan tipe dan kejelasan kode.
Memahami Middleware Express
Fungsi middleware adalah fungsi yang memiliki akses ke objek permintaan (req), objek respons (res), dan fungsi middleware berikutnya dalam siklus permintaan-respons aplikasi. Fungsi middleware dapat melakukan tugas-tugas berikut:
- Menjalankan kode apa pun.
- Membuat perubahan pada objek permintaan dan respons.
- Mengakhiri siklus permintaan-respons.
- Memanggil fungsi middleware berikutnya dalam tumpukan.
Fungsi middleware dijalankan secara berurutan saat ditambahkan ke aplikasi Express. Kasus penggunaan umum untuk middleware meliputi:
- Mencatat permintaan.
- Mengesahkan pengguna.
- Mengotorisasi akses ke sumber daya.
- Memvalidasi data permintaan.
- Menangani kesalahan.
Middleware TypeScript Dasar
Dalam aplikasi Express TypeScript dasar, fungsi middleware mungkin terlihat seperti ini:
import { Request, Response, NextFunction } from 'express';
function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
console.log(`Request: ${req.method} ${req.url}`);
next();
}
export default loggerMiddleware;
Middleware sederhana ini mencatat metode dan URL permintaan ke konsol. Mari kita uraikan anotasi tipe:
Request: Mewakili objek permintaan Express.Response: Mewakili objek respons Express.NextFunction: Sebuah fungsi yang, ketika dipanggil, menjalankan middleware berikutnya dalam tumpukan.
Anda dapat menggunakan middleware ini dalam aplikasi Express Anda seperti ini:
import express from 'express';
import loggerMiddleware from './middleware/loggerMiddleware';
const app = express();
const port = 3000;
app.use(loggerMiddleware);
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
Pola Tipe Lanjutan untuk Middleware
Meskipun contoh middleware dasar berfungsi, ia kurang fleksibel dan keamanan tipe untuk skenario yang lebih kompleks. Mari kita jelajahi pola tipe lanjutan yang meningkatkan pengembangan middleware dengan TypeScript.
1. Tipe Permintaan/Respons Kustom
Seringkali, Anda perlu memperluas objek Request atau Response dengan properti kustom. Misalnya, setelah otentikasi, Anda mungkin ingin menambahkan properti user ke objek Request. TypeScript memungkinkan Anda untuk menambahkan tipe yang ada menggunakan penggabungan deklarasi.
// src/types/express/index.d.ts
import { Request as ExpressRequest } from 'express';
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
// ... properti pengguna lainnya
};
}
}
}
export {}; // Ini diperlukan untuk menjadikan file ini sebagai modul
Dalam contoh ini, kami menambahkan antarmuka Express.Request untuk menyertakan properti user opsional. Sekarang, dalam middleware otentikasi Anda, Anda dapat mengisi properti ini:
import { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Simulasikan logika otentikasi
const userId = req.headers['x-user-id'] as string; // Atau ambil dari token, dll.
if (userId) {
// Dalam aplikasi nyata, Anda akan mengambil pengguna dari database
req.user = {
id: userId,
email: `user${userId}@example.com`
};
next();
} else {
res.status(401).send('Unauthorized');
}
}
export default authenticationMiddleware;
Dan dalam penangan rute Anda, Anda dapat dengan aman mengakses properti req.user:
import express, { Request, Response } from 'express';
import authenticationMiddleware from './middleware/authenticationMiddleware';
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/profile', (req: Request, res: Response) => {
if (req.user) {
res.send(`Hello, ${req.user.email}! Your user ID is ${req.user.id}`);
} else {
// Ini seharusnya tidak pernah terjadi jika middleware berfungsi dengan benar
res.status(500).send('Internal Server Error');
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
2. Pabrik Middleware
Pabrik middleware adalah fungsi yang mengembalikan fungsi middleware. Pola ini berguna ketika Anda perlu mengonfigurasi middleware dengan opsi atau dependensi tertentu. Misalnya, pertimbangkan middleware logging yang mencatat pesan ke file tertentu:
import { Request, Response, NextFunction } from 'express';
import fs from 'fs';
import path from 'path';
function createLoggingMiddleware(logFilePath: string) {
return (req: Request, res: Response, next: NextFunction) => {
const logMessage = `[${new Date().toISOString()}] Request: ${req.method} ${req.url}\n`;
fs.appendFile(logFilePath, logMessage, (err) => {
if (err) {
console.error('Error writing to log file:', err);
}
next();
});
};
}
export default createLoggingMiddleware;
Anda dapat menggunakan pabrik middleware ini seperti ini:
import express from 'express';
import createLoggingMiddleware from './middleware/loggingMiddleware';
const app = express();
const port = 3000;
const logFilePath = path.join(__dirname, 'logs', 'requests.log');
app.use(createLoggingMiddleware(logFilePath));
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
3. Middleware Asinkron
Fungsi middleware seringkali perlu melakukan operasi asinkron, seperti kueri database atau panggilan API. Untuk menangani operasi asinkron dengan benar, Anda perlu memastikan bahwa fungsi next dipanggil setelah operasi asinkron selesai. Anda dapat mencapai ini menggunakan async/await atau Promise.
import { Request, Response, NextFunction } from 'express';
async function asyncMiddleware(req: Request, res: Response, next: NextFunction) {
try {
// Simulasikan operasi asinkron
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Asynchronous operation completed');
next();
} catch (error) {
next(error); // Teruskan kesalahan ke middleware penanganan kesalahan
}
}
export default asyncMiddleware;
Penting: Ingatlah untuk menangani kesalahan di dalam middleware asinkron Anda dan meneruskannya ke middleware penanganan kesalahan menggunakan next(error). Ini memastikan bahwa kesalahan ditangani dan dicatat dengan benar.
4. Middleware Penanganan Kesalahan
Middleware penanganan kesalahan adalah jenis middleware khusus yang menangani kesalahan yang terjadi selama siklus permintaan-respons. Fungsi middleware penanganan kesalahan memiliki empat argumen: err, req, res, dan next.
import { Request, Response, NextFunction } from 'express';
function errorHandler(err: any, req: Request, res: Response, next: NextFunction) {
console.error(err.stack);
res.status(500).send('Something went wrong!');
}
export default errorHandler;
Anda harus mendaftarkan middleware penanganan kesalahan setelah semua middleware dan penangan rute lainnya. Express mengidentifikasi middleware penanganan kesalahan berdasarkan keberadaan empat argumen.
import express from 'express';
import asyncMiddleware from './middleware/asyncMiddleware';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(asyncMiddleware);
app.get('/', (req, res) => {
throw new Error('Simulated error!'); // Simulasikan kesalahan
});
app.use(errorHandler); // Middleware penanganan kesalahan HARUS didaftarkan terakhir
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
5. Middleware Validasi Permintaan
Validasi permintaan adalah aspek penting dalam membangun API yang aman dan andal. Middleware dapat digunakan untuk memvalidasi data permintaan yang masuk dan memastikan bahwa data tersebut memenuhi kriteria tertentu sebelum mencapai penangan rute Anda. Pustaka seperti joi atau express-validator dapat digunakan untuk validasi permintaan.
Berikut adalah contoh menggunakan express-validator:
import { Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator';
const validateCreateUserRequest = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
}
];
export default validateCreateUserRequest;
Middleware ini memvalidasi bidang email dan password dalam badan permintaan. Jika validasi gagal, ia mengembalikan respons 400 Bad Request dengan array pesan kesalahan. Anda dapat menggunakan middleware ini dalam penangan rute Anda seperti ini:
import express from 'express';
import validateCreateUserRequest from './middleware/validateCreateUserRequest';
const app = express();
const port = 3000;
app.post('/users', validateCreateUserRequest, (req, res) => {
// Jika validasi berhasil, buat pengguna
res.send('User created successfully!');
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
6. Injeksi Dependensi untuk Middleware
Ketika fungsi middleware Anda bergantung pada layanan atau konfigurasi eksternal, injeksi dependensi dapat membantu meningkatkan keterujian dan pemeliharaan. Anda dapat menggunakan wadah injeksi dependensi seperti tsyringe atau cukup meneruskan dependensi sebagai argumen ke pabrik middleware Anda.
Berikut adalah contoh menggunakan pabrik middleware dengan injeksi dependensi:
// src/services/UserService.ts
export class UserService {
async createUser(email: string, password: string): Promise {
// Dalam aplikasi nyata, Anda akan menyimpan pengguna ke dalam database
console.log(`Creating user with email: ${email} and password: ${password}`);
await new Promise(resolve => setTimeout(resolve, 500)); // Simulasikan operasi database
}
}
// src/middleware/createUserMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { UserService } from '../services/UserService';
function createCreateUserMiddleware(userService: UserService) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
await userService.createUser(email, password);
res.status(201).send('User created successfully!');
} catch (error) {
next(error);
}
};
}
export default createCreateUserMiddleware;
// src/app.ts
import express from 'express';
import createCreateUserMiddleware from './middleware/createUserMiddleware';
import { UserService } from './services/UserService';
import errorHandler from './middleware/errorHandler';
const app = express();
const port = 3000;
app.use(express.json()); // Parse badan permintaan JSON
const userService = new UserService();
const createUserMiddleware = createCreateUserMiddleware(userService);
app.post('/users', createUserMiddleware);
app.use(errorHandler);
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
Praktik Terbaik untuk Middleware TypeScript
- Jaga agar fungsi middleware tetap kecil dan fokus. Setiap fungsi middleware harus memiliki satu tanggung jawab.
- Gunakan nama deskriptif untuk fungsi middleware Anda. Nama harus dengan jelas menunjukkan apa yang dilakukan middleware.
- Tangani kesalahan dengan benar. Selalu tangkap kesalahan dan teruskan ke middleware penanganan kesalahan menggunakan
next(error). - Gunakan tipe permintaan/respons kustom untuk meningkatkan keamanan tipe. Tambahkan antarmuka
RequestdanResponsedengan properti kustom sesuai kebutuhan. - Gunakan pabrik middleware untuk mengonfigurasi middleware dengan opsi tertentu.
- Dokumentasikan fungsi middleware Anda. Jelaskan apa yang dilakukan middleware dan bagaimana seharusnya digunakan.
- Uji fungsi middleware Anda secara menyeluruh. Tulis tes unit untuk memastikan bahwa fungsi middleware Anda berfungsi dengan benar.
Kesimpulan
TypeScript secara signifikan meningkatkan pengembangan middleware Express dengan menambahkan pengetikan statis, meningkatkan pemeliharaan kode, dan menangkap kesalahan lebih awal. Dengan menguasai pola tipe lanjutan seperti tipe permintaan/respons kustom, pabrik middleware, middleware asinkron, middleware penanganan kesalahan, dan middleware validasi permintaan, Anda dapat membangun aplikasi Express yang kuat, skalabel, dan aman tipe. Ingatlah untuk mengikuti praktik terbaik untuk menjaga fungsi middleware Anda kecil, fokus, dan terdokumentasi dengan baik.