Tingkatkan aplikasi Express.js Anda dengan keamanan tipe yang kuat menggunakan TypeScript. Panduan ini mencakup definisi route handler, pengetikan middleware, dan praktik terbaik.
Integrasi TypeScript Express: Keamanan Tipe Route Handler
TypeScript telah menjadi landasan pengembangan JavaScript modern, menawarkan kemampuan pengetikan statis yang meningkatkan kualitas kode, kemudahan pemeliharaan, dan skalabilitas. Ketika dikombinasikan dengan Express.js, sebuah framework aplikasi web Node.js yang populer, TypeScript dapat secara signifikan meningkatkan ketahanan API backend Anda. Panduan komprehensif ini mengeksplorasi bagaimana memanfaatkan TypeScript untuk mencapai keamanan tipe route handler dalam aplikasi Express.js, memberikan contoh praktis dan praktik terbaik untuk membangun API yang kuat dan mudah dipelihara untuk audiens global.
Mengapa Keamanan Tipe Penting dalam Express.js
Dalam bahasa dinamis seperti JavaScript, kesalahan seringkali tertangkap saat runtime, yang dapat menyebabkan perilaku tak terduga dan masalah yang sulit di-debug. TypeScript mengatasi hal ini dengan memperkenalkan pengetikan statis, memungkinkan Anda menangkap kesalahan selama pengembangan sebelum sampai ke produksi. Dalam konteks Express.js, keamanan tipe sangat penting untuk route handler, di mana Anda berurusan dengan objek permintaan dan respons, parameter query, dan body permintaan. Penanganan elemen-elemen ini yang salah dapat menyebabkan aplikasi crash, kerusakan data, dan kerentanan keamanan.
- Deteksi Kesalahan Awal: Tangkap kesalahan terkait tipe selama pengembangan, mengurangi kemungkinan kejutan saat runtime.
- Peningkatan Kemudahan Pemeliharaan Kode: Anotasi tipe membuat kode lebih mudah dipahami dan difaktorkan ulang.
- Peningkatan Penyelesaian Kode dan Peralatan: IDE dapat memberikan saran dan pemeriksaan kesalahan yang lebih baik dengan informasi tipe.
- Pengurangan Bug: Keamanan tipe membantu mencegah kesalahan pemrograman umum, seperti meneruskan tipe data yang salah ke fungsi.
Menyiapkan Proyek TypeScript Express.js
Sebelum menyelami keamanan tipe route handler, mari kita siapkan proyek TypeScript Express.js dasar. Ini akan berfungsi sebagai fondasi untuk contoh kita.
Prasyarat
- Node.js dan npm (Node Package Manager) terinstal. Anda dapat mengunduhnya dari situs web Node.js resmi. Pastikan Anda memiliki versi terbaru untuk kompatibilitas optimal.
- Editor kode seperti Visual Studio Code, yang menawarkan dukungan TypeScript yang sangat baik.
Inisialisasi Proyek
- Buat direktori proyek baru:
mkdir typescript-express-app && cd typescript-express-app - Inisialisasi proyek npm baru:
npm init -y - Instal TypeScript dan Express.js:
npm install typescript express - Instal file deklarasi TypeScript untuk Express.js (penting untuk keamanan tipe):
npm install @types/express @types/node - Inisialisasi TypeScript:
npx tsc --init(Ini membuat filetsconfig.json, yang mengonfigurasi compiler TypeScript.)
Mengonfigurasi TypeScript
Buka file tsconfig.json dan konfigurasikan dengan tepat. Berikut adalah contoh konfigurasi:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Konfigurasi utama yang perlu diperhatikan:
target: Menentukan versi target ECMAScript.es6adalah titik awal yang baik.module: Menentukan pembuatan kode modul.commonjsadalah pilihan umum untuk Node.js.outDir: Menentukan direktori output untuk file JavaScript yang dikompilasi.rootDir: Menentukan direktori root file sumber TypeScript Anda.strict: Mengaktifkan semua opsi pemeriksaan tipe ketat untuk peningkatan keamanan tipe. Ini sangat direkomendasikan.esModuleInterop: Mengaktifkan interoperabilitas antara CommonJS dan ES Modules.
Membuat Titik Masuk
Buat direktori src dan tambahkan file index.ts:
mkdir src
touch src/index.ts
Isi src/index.ts dengan pengaturan server Express.js dasar:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
app.get('/', (req: Request, res: Response) => {
res.send('Hello, TypeScript Express!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Menambahkan Skrip Build
Tambahkan skrip build ke file package.json Anda untuk mengompilasi kode TypeScript:
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "npm run build && npm run start"
}
Sekarang Anda dapat menjalankan npm run dev untuk membangun dan memulai server.
Keamanan Tipe Route Handler: Mendefinisikan Tipe Permintaan dan Respons
Inti dari keamanan tipe route handler terletak pada pendefinisian tipe yang tepat untuk objek Request dan Response. Express.js menyediakan tipe generik untuk objek-objek ini yang memungkinkan Anda menentukan tipe parameter query, body permintaan, dan parameter route.
Tipe Route Handler Dasar
Mari kita mulai dengan route handler sederhana yang mengharapkan nama sebagai parameter query:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
interface NameQuery {
name: string;
}
app.get('/hello', (req: Request, res: Response) => {
const name = req.query.name;
if (!name) {
return res.status(400).send('Name parameter is required.');
}
res.send(`Hello, ${name}!`);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Dalam contoh ini:
Request<any, any, any, NameQuery>mendefinisikan tipe untuk objek permintaan.anypertama mewakili parameter route (mis.,/users/:id).anykedua mewakili tipe body respons.anyketiga mewakili tipe body permintaan.NameQueryadalah antarmuka yang mendefinisikan struktur parameter query.
Dengan mendefinisikan antarmuka NameQuery, TypeScript sekarang dapat memverifikasi bahwa properti req.query.name ada dan bertipe string. Jika Anda mencoba mengakses properti yang tidak ada atau menetapkan nilai dengan tipe yang salah, TypeScript akan menandai kesalahan.
Menangani Body Permintaan
Untuk route yang menerima body permintaan (mis., POST, PUT, PATCH), Anda dapat mendefinisikan antarmuka untuk body permintaan dan menggunakannya dalam tipe Request:
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
const app = express();
const port = 3000;
app.use(bodyParser.json()); // Important for parsing JSON request bodies
interface CreateUserRequest {
firstName: string;
lastName: string;
email: string;
}
app.post('/users', (req: Request, res: Response) => {
const { firstName, lastName, email } = req.body;
// Validate the request body
if (!firstName || !lastName || !email) {
return res.status(400).send('Missing required fields.');
}
// Process the user creation (e.g., save to database)
console.log(`Creating user: ${firstName} ${lastName} (${email})`);
res.status(201).send('User created successfully.');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Dalam contoh ini:
CreateUserRequestmendefinisikan struktur body permintaan yang diharapkan.app.use(bodyParser.json())sangat penting untuk mengurai body permintaan JSON. Tanpa itu,req.bodyakan tidak terdefinisi.- Tipe
RequestsekarangRequest<any, any, CreateUserRequest>, menunjukkan bahwa body permintaan harus sesuai dengan antarmukaCreateUserRequest.
TypeScript sekarang akan memastikan bahwa objek req.body berisi properti yang diharapkan (firstName, lastName, dan email) dan bahwa tipenya benar. Ini secara signifikan mengurangi risiko kesalahan runtime yang disebabkan oleh data body permintaan yang salah.
Menangani Parameter Route
Untuk route dengan parameter (mis., /users/:id), Anda dapat mendefinisikan antarmuka untuk parameter route dan menggunakannya dalam tipe Request:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
interface UserParams {
id: string;
}
interface User {
id: string;
firstName: string;
lastName: string;
email: string;
}
const users: User[] = [
{ id: '1', firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com' },
{ id: '2', firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com' },
];
app.get('/users/:id', (req: Request, res: Response) => {
const userId = req.params.id;
const user = users.find(u => u.id === userId);
if (!user) {
return res.status(404).send('User not found.');
}
res.json(user);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Dalam contoh ini:
UserParamsmendefinisikan struktur parameter route, menentukan bahwa parameteridharus berupa string.- Tipe
RequestsekarangRequest<UserParams>, menunjukkan bahwa objekreq.paramsharus sesuai dengan antarmukaUserParams.
TypeScript sekarang akan memastikan bahwa properti req.params.id ada dan bertipe string. Ini membantu mencegah kesalahan yang disebabkan oleh akses ke parameter route yang tidak ada atau menggunakannya dengan tipe yang salah.
Menentukan Tipe Respons
Meskipun berfokus pada keamanan tipe permintaan sangat penting, mendefinisikan tipe respons juga meningkatkan kejelasan kode dan membantu mencegah inkonsistensi. Anda dapat menentukan tipe data yang Anda kirim kembali dalam respons.
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
interface User {
id: string;
firstName: string;
lastName: string;
email: string;
}
const users: User[] = [
{ id: '1', firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com' },
{ id: '2', firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com' },
];
app.get('/users', (req: Request, res: Response) => {
res.json(users);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Di sini, Response<User[]> menentukan bahwa body respons harus berupa array objek User. Ini membantu memastikan bahwa Anda secara konsisten mengirim struktur data yang benar dalam respons API Anda. Jika Anda mencoba mengirim data yang tidak sesuai dengan tipe `User[]`, TypeScript akan mengeluarkan peringatan.
Keamanan Tipe Middleware
Fungsi middleware sangat penting untuk menangani masalah lintas-potong dalam aplikasi Express.js. Memastikan keamanan tipe dalam middleware sama pentingnya dengan dalam route handler.
Mengetik Fungsi Middleware
Struktur dasar fungsi middleware di TypeScript mirip dengan route handler:
import express, { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Authentication logic
const isAuthenticated = true; // Replace with actual authentication check
if (isAuthenticated) {
next(); // Proceed to the next middleware or route handler
} else {
res.status(401).send('Unauthorized');
}
}
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/', (req: Request, res: Response) => {
res.send('Hello, authenticated user!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Dalam contoh ini:
NextFunctionadalah tipe yang disediakan oleh Express.js yang mewakili fungsi middleware berikutnya dalam rantai.- Fungsi middleware mengambil objek
RequestdanResponseyang sama dengan route handler.
Menambah Objek Permintaan
Terkadang, Anda mungkin ingin menambahkan properti kustom ke objek Request di middleware Anda. Misalnya, middleware otentikasi mungkin menambahkan properti user ke objek permintaan. Untuk melakukan ini dengan cara yang aman tipe, Anda perlu menambah antarmuka Request.
import express, { Request, Response, NextFunction } from 'express';
interface User {
id: string;
username: string;
email: string;
}
// Augment the Request interface
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Authentication logic (replace with actual authentication check)
const user: User = { id: '123', username: 'johndoe', email: 'john.doe@example.com' };
req.user = user; // Add the user to the request object
next(); // Proceed to the next middleware or route handler
}
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/', (req: Request, res: Response) => {
const username = req.user?.username || 'Guest';
res.send(`Hello, ${username}!`);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Dalam contoh ini:
- Kami menggunakan deklarasi global untuk menambah antarmuka
Express.Request. - Kami menambahkan properti
useropsional bertipeUserke antarmukaRequest. - Sekarang, Anda dapat mengakses properti
req.userdi route handler Anda tanpa TypeScript mengeluh. Tanda `?` di `req.user?.username` sangat penting untuk menangani kasus di mana pengguna tidak diautentikasi, mencegah potensi kesalahan.
Praktik Terbaik untuk Integrasi TypeScript Express
Untuk memaksimalkan manfaat TypeScript dalam aplikasi Express.js Anda, ikuti praktik terbaik ini:
- Aktifkan Mode Ketat: Gunakan opsi
"strict": truedalam filetsconfig.jsonAnda untuk mengaktifkan semua opsi pemeriksaan tipe ketat. Ini membantu menangkap potensi kesalahan lebih awal dan memastikan tingkat keamanan tipe yang lebih tinggi. - Gunakan Antarmuka dan Alias Tipe: Definisikan antarmuka dan alias tipe untuk mewakili struktur data Anda. Ini membuat kode Anda lebih mudah dibaca dan dipelihara.
- Gunakan Tipe Generik: Manfaatkan tipe generik untuk membuat komponen yang dapat digunakan kembali dan aman tipe.
- Tulis Unit Test: Tulis unit test untuk memverifikasi kebenaran kode Anda dan memastikan bahwa anotasi tipe Anda akurat. Pengujian sangat penting untuk menjaga kualitas kode.
- Gunakan Linter dan Pemformat: Gunakan linter (seperti ESLint) dan pemformat (seperti Prettier) untuk memberlakukan gaya pengkodean yang konsisten dan menangkap potensi kesalahan.
- Hindari Tipe
any: Minimalkan penggunaan tipeany, karena ini melewati pemeriksaan tipe dan menggagalkan tujuan penggunaan TypeScript. Hanya gunakan jika benar-benar diperlukan, dan pertimbangkan untuk menggunakan tipe atau generik yang lebih spesifik jika memungkinkan. - Strukturkan proyek Anda secara logis: Atur proyek Anda ke dalam modul atau folder berdasarkan fungsionalitas. Ini akan meningkatkan kemudahan pemeliharaan dan skalabilitas aplikasi Anda.
- Gunakan Injeksi Dependensi: Pertimbangkan untuk menggunakan kontainer injeksi dependensi untuk mengelola dependensi aplikasi Anda. Ini dapat membuat kode Anda lebih mudah diuji dan dipelihara. Pustaka seperti InversifyJS adalah pilihan populer.
Konsep TypeScript Tingkat Lanjut untuk Express.js
Menggunakan Dekorator
Dekorator menyediakan cara yang ringkas dan ekspresif untuk menambahkan metadata ke kelas dan fungsi. Anda dapat menggunakan dekorator untuk menyederhanakan pendaftaran route di Express.js.
Pertama, Anda perlu mengaktifkan dekorator eksperimental di file tsconfig.json Anda dengan menambahkan "experimentalDecorators": true ke compilerOptions.
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true
}
}
Kemudian, Anda dapat membuat dekorator kustom untuk mendaftarkan route:
import express, { Router, Request, Response } from 'express';
function route(method: string, path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target.__router__) {
target.__router__ = Router();
}
target.__router__[method](path, descriptor.value);
};
}
class UserController {
@route('get', '/users')
getUsers(req: Request, res: Response) {
res.send('List of users');
}
@route('post', '/users')
createUser(req: Request, res: Response) {
res.status(201).send('User created');
}
public getRouter() {
return this.__router__;
}
}
const userController = new UserController();
const app = express();
const port = 3000;
app.use('/', userController.getRouter());
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Dalam contoh ini:
- Dekorator
routemengambil metode HTTP dan path sebagai argumen. - Ini mendaftarkan metode yang didekorasi sebagai route handler pada router yang terkait dengan kelas.
- Ini menyederhanakan pendaftaran route dan membuat kode Anda lebih mudah dibaca.
Menggunakan Type Guard Kustom
Type guard adalah fungsi yang mempersempit tipe variabel dalam lingkup tertentu. Anda dapat menggunakan type guard kustom untuk memvalidasi body permintaan atau parameter query.
interface Product {
id: string;
name: string;
price: number;
}
function isProduct(obj: any): obj is Product {
return typeof obj === 'object' &&
obj !== null &&
typeof obj.id === 'string' &&
typeof obj.name === 'string' &&
typeof obj.price === 'number';
}
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
const app = express();
const port = 3000;
app.use(bodyParser.json());
app.post('/products', (req: Request, res: Response) => {
if (!isProduct(req.body)) {
return res.status(400).send('Invalid product data');
}
const product: Product = req.body;
console.log(`Creating product: ${product.name}`);
res.status(201).send('Product created');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Dalam contoh ini:
- Fungsi
isProductadalah type guard kustom yang memeriksa apakah suatu objek sesuai dengan antarmukaProduct. - Di dalam route handler
/products, fungsiisProductdigunakan untuk memvalidasi body permintaan. - Jika body permintaan adalah produk yang valid, TypeScript tahu bahwa
req.bodybertipeProductdi dalam blokif.
Mempertimbangkan Pertimbangan Global dalam Desain API
Saat mendesain API untuk audiens global, beberapa faktor harus dipertimbangkan untuk memastikan aksesibilitas, kegunaan, dan sensitivitas budaya.
- Lokalisasi dan Internasionalisasi (i18n dan L10n):
- Negosiasi Konten: Mendukung beberapa bahasa dan wilayah melalui negosiasi konten berdasarkan header
Accept-Language. - Format Tanggal dan Waktu: Gunakan format ISO 8601 untuk representasi tanggal dan waktu untuk menghindari ambiguitas di berbagai wilayah.
- Format Angka: Tangani format angka sesuai dengan lokal pengguna (mis., pemisah desimal dan pemisah ribuan).
- Penanganan Mata Uang: Mendukung beberapa mata uang dan memberikan informasi nilai tukar jika diperlukan.
- Arah Teks: Akomodasi bahasa kanan-ke-kiri (RTL) seperti bahasa Arab dan Ibrani.
- Negosiasi Konten: Mendukung beberapa bahasa dan wilayah melalui negosiasi konten berdasarkan header
- Zona Waktu:
- Simpan tanggal dan waktu dalam UTC (Coordinated Universal Time) di sisi server.
- Izinkan pengguna untuk menentukan zona waktu pilihan mereka dan mengonversi tanggal dan waktu sesuai dengan itu di sisi klien.
- Gunakan pustaka seperti
moment-timezoneuntuk menangani konversi zona waktu.
- Penyandian Karakter:
- Gunakan penyandian UTF-8 untuk semua data teks untuk mendukung berbagai karakter dari berbagai bahasa.
- Pastikan bahwa database Anda dan sistem penyimpanan data lainnya dikonfigurasi untuk menggunakan UTF-8.
- Aksesibilitas:
- Ikuti panduan aksesibilitas (mis., WCAG) untuk membuat API Anda dapat diakses oleh pengguna penyandang disabilitas.
- Berikan pesan kesalahan yang jelas dan deskriptif yang mudah dipahami.
- Gunakan elemen HTML semantik dan atribut ARIA dalam dokumentasi API Anda.
- Sensitivitas Budaya:
- Hindari penggunaan referensi, idiom, atau humor khusus budaya yang mungkin tidak dipahami oleh semua pengguna.
- Berhati-hatilah terhadap perbedaan budaya dalam gaya dan preferensi komunikasi.
- Pertimbangkan potensi dampak API Anda pada kelompok budaya yang berbeda dan hindari melanggengkan stereotip atau bias.
- Privasi dan Keamanan Data:
- Patuhi peraturan privasi data seperti GDPR (General Data Protection Regulation) dan CCPA (California Consumer Privacy Act).
- Terapkan mekanisme otentikasi dan otorisasi yang kuat untuk melindungi data pengguna.
- Enkripsi data sensitif baik saat transit maupun saat istirahat.
- Berikan pengguna kendali atas data mereka dan izinkan mereka untuk mengakses, memodifikasi, dan menghapus data mereka.
- Dokumentasi API:
- Berikan dokumentasi API yang komprehensif dan terorganisasi dengan baik yang mudah dipahami dan dinavigasi.
- Gunakan alat seperti Swagger/OpenAPI untuk menghasilkan dokumentasi API interaktif.
- Sertakan contoh kode dalam beberapa bahasa pemrograman untuk melayani audiens yang beragam.
- Terjemahkan dokumentasi API Anda ke dalam beberapa bahasa untuk menjangkau audiens yang lebih luas.
- Penanganan Kesalahan:
- Berikan pesan kesalahan yang spesifik dan informatif. Hindari pesan kesalahan generik seperti "Terjadi kesalahan."
- Gunakan kode status HTTP standar untuk menunjukkan jenis kesalahan (mis., 400 untuk Permintaan Buruk, 401 untuk Tidak Sah, 500 untuk Kesalahan Server Internal).
- Sertakan kode atau pengidentifikasi kesalahan yang dapat digunakan untuk melacak dan men-debug masalah.
- Catat kesalahan di sisi server untuk debugging dan pemantauan.
- Pembatasan Tingkat: Terapkan pembatasan tingkat untuk melindungi API Anda dari penyalahgunaan dan memastikan penggunaan yang adil.
- Pemberian Versi: Gunakan pemberian versi API untuk memungkinkan perubahan yang kompatibel dengan versi sebelumnya dan menghindari pemutusan klien yang ada.
Kesimpulan
Integrasi TypeScript Express secara signifikan meningkatkan keandalan dan kemudahan pemeliharaan API backend Anda. Dengan memanfaatkan keamanan tipe dalam route handler dan middleware, Anda dapat menangkap kesalahan lebih awal dalam proses pengembangan dan membangun aplikasi yang lebih kuat dan terukur untuk audiens global. Dengan mendefinisikan tipe permintaan dan respons, Anda memastikan bahwa API Anda mematuhi struktur data yang konsisten, mengurangi kemungkinan kesalahan runtime. Ingatlah untuk mematuhi praktik terbaik seperti mengaktifkan mode ketat, menggunakan antarmuka dan alias tipe, dan menulis unit test untuk memaksimalkan manfaat TypeScript. Selalu pertimbangkan faktor global seperti lokalisasi, zona waktu, dan sensitivitas budaya untuk memastikan API Anda dapat diakses dan digunakan di seluruh dunia.