Подобрете Express.js приложенията си със здрава типизована безопасност с TypeScript. Това ръководство обхваща дефиниции на обработващи маршрути, типизация на middleware и най-добри практики.
TypeScript Express Интеграция: Типова безопасност на обработващите маршрути
TypeScript се превърна в крайъгълен камък на модерната JavaScript разработка, предлагайки възможности за статично типизиране, които подобряват качеството на кода, поддръжката и мащабируемостта. Когато се комбинира с Express.js, популярен уеб приложение фреймуърк за Node.js, TypeScript може значително да подобри здравината на вашите бекенд API-та. Това изчерпателно ръководство изследва как да се използва TypeScript за постигане на типизована безопасност на обработващите маршрути в Express.js приложения, предоставяйки практически примери и най-добри практики за изграждане на здрави и поддържаеми API-та за глобална аудитория.
Защо типизованата безопасност има значение в Express.js
В динамични езици като JavaScript, грешките често се улавят по време на изпълнение, което може да доведе до неочаквано поведение и трудни за отстраняване проблеми. TypeScript адресира това чрез въвеждане на статично типизиране, което ви позволява да улавяте грешки по време на разработка, преди да достигнат до продукция. В контекста на Express.js, типизованата безопасност е особено важна за обработващите маршрути, където работите с обекти за заявки и отговори, параметри на заявки и тела на заявки. Неправилното боравене с тези елементи може да доведе до сривове на приложението, повреда на данни и уязвимости в сигурността.
- Ранно откриване на грешки: Улавяйте грешки, свързани с типове, по време на разработка, намалявайки вероятността от изненади по време на изпълнение.
- Подобрена поддръжка на кода: Типовите анотации правят кода по-лесен за разбиране и рефакториране.
- Подобрено завършване на код и инструменти: IDE-тата могат да предоставят по-добри предложения и проверка на грешки с информация за типовете.
- Намалени бъгове: Типизованата безопасност помага за предотвратяване на често срещани програмни грешки, като например предаване на грешни типове данни на функции.
Настройване на TypeScript Express.js Проект
Преди да се потопим в типизованата безопасност на обработващите маршрути, нека настроим основен TypeScript Express.js проект. Това ще служи като основа за нашите примери.
Предварителни изисквания
- Инсталиран Node.js и npm (Node Package Manager). Можете да ги изтеглите от официалния уебсайт на Node.js. Уверете се, че имате скорошна версия за оптимална съвместимост.
- Редактор на код като Visual Studio Code, който предлага отлична поддръжка за TypeScript.
Инициализация на проекта
- Създайте нова директория за проекта:
mkdir typescript-express-app && cd typescript-express-app - Инициализирайте нов npm проект:
npm init -y - Инсталирайте TypeScript и Express.js:
npm install typescript express - Инсталирайте файловете с декларации за TypeScript за Express.js (важно за типизована безопасност):
npm install @types/express @types/node - Инициализирайте TypeScript:
npx tsc --init(Това създава файлtsconfig.json, който конфигурира TypeScript компилатора.)
Конфигуриране на TypeScript
Отворете файла tsconfig.json и го конфигурирайте подходящо. Ето примерна конфигурация:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Ключови конфигурации, които трябва да се отбележат:
target: Посочва версията на ECMAScript целта.es6е добра отправна точка.module: Посочва генерирането на модулен код.commonjsе често срещан избор за Node.js.outDir: Посочва изходната директория за компилираните JavaScript файлове.rootDir: Посочва основната директория на вашите TypeScript изходни файлове.strict: Активира всички опции за строга типизована проверка за подобрена типизована безопасност. Това е силно препоръчително.esModuleInterop: Активира оперативната съвместимост между CommonJS и ES модули.
Създаване на входната точка
Създайте директория src и добавете файл index.ts:
mkdir src
touch src/index.ts
Попълнете src/index.ts с основна настройка на Express.js сървър:
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}`);
});
Добавяне на скрипт за компилация
Добавете скрипт за компилация към вашия файл package.json, за да компилирате TypeScript кода:
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "npm run build && npm run start"
}
Сега можете да изпълните npm run dev, за да компилирате и стартирате сървъра.
Типизована безопасност на обработващите маршрути: Дефиниране на типове за Request и Response
Ядрото на типизованата безопасност на обработващите маршрути се крие в правилното дефиниране на типовете за обектите Request и Response. Express.js предоставя генерични типове за тези обекти, които ви позволяват да специфицирате типовете на параметрите на заявката, тялото на заявката и параметрите на маршрута.
Основни типове на обработващите маршрути
Нека започнем с прост обработващ маршрут, който очаква име като параметър на заявката:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
interface NameQuery {
name: string;
}
app.get('/hello', (req: Request<any, any, any, NameQuery>, 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}`);
});
В този пример:
Request<any, any, any, NameQuery>дефинира типа за обекта на заявката.- Първото
anyпредставлява параметрите на маршрута (напр./users/:id). - Второто
anyпредставлява типа на тялото на отговора. - Третото
anyпредставлява типа на тялото на заявката. NameQueryе интерфейс, който дефинира структурата на параметрите на заявката.
Чрез дефиниране на интерфейса NameQuery, TypeScript вече може да провери дали свойството req.query.name съществува и дали е от тип string. Ако се опитате да получите достъп до несъществуващо свойство или да присвоите стойност от грешен тип, TypeScript ще маркира грешка.
Обработка на тела на заявки
За маршрути, които приемат тела на заявки (напр. POST, PUT, PATCH), можете да дефинирате интерфейс за тялото на заявката и да го използвате в типа Request:
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
const app = express();
const port = 3000;
app.use(bodyParser.json()); // Важно за парсване на JSON тела на заявки
interface CreateUserRequest {
firstName: string;
lastName: string;
email: string;
}
app.post('/users', (req: Request<any, any, CreateUserRequest>, res: Response) => {
const { firstName, lastName, email } = req.body;
// Валидиране на тялото на заявката
if (!firstName || !lastName || !email) {
return res.status(400).send('Missing required fields.');
}
// Обработка на създаването на потребител (напр. запис в база данни)
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}`);
});
В този пример:
CreateUserRequestдефинира структурата на очакваното тяло на заявката.app.use(bodyParser.json())е от решаващо значение за парсване на JSON тела на заявки. Без негоreq.bodyще бъде undefined.- Типът
Requestвече еRequest<any, any, CreateUserRequest>, което показва, че тялото на заявката трябва да съответства на интерфейсаCreateUserRequest.
TypeScript сега ще гарантира, че обектът req.body съдържа очакваните свойства (firstName, lastName и email) и че техните типове са правилни. Това значително намалява риска от грешки по време на изпълнение, причинени от неправилни данни в тялото на заявката.
Обработка на параметри на маршрути
За маршрути с параметри (напр. /users/:id), можете да дефинирате интерфейс за параметрите на маршрута и да го използвате в типа 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<UserParams>, 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}`);
});
В този пример:
UserParamsдефинира структурата на параметрите на маршрута, като указва, че параметърътidтрябва да бъде низ.- Типът
Requestсега еRequest<UserParams>, което показва, че обектътreq.paramsтрябва да съответства на интерфейсаUserParams.
TypeScript сега ще гарантира, че свойството req.params.id съществува и е от тип string. Това помага за предотвратяване на грешки, причинени от достъп до несъществуващи параметри на маршрута или използването им с грешни типове.
Специфициране на типове на отговори
Докато фокусирането върху типизованата безопасност на заявките е важно, дефинирането на типове на отговори също подобрява яснотата на кода и помага за предотвратяване на несъответствия. Можете да дефинирате типа на данните, които изпращате обратно в отговора.
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<User[]>) => {
res.json(users);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Тук Response<User[]> указва, че тялото на отговора трябва да бъде масив от User обекти. Това помага да се гарантира, че последователно изпращате правилната структура на данни във вашите API отговори. Ако се опитате да изпратите данни, които не съответстват на типа User[], TypeScript ще издаде предупреждение.
Типизована безопасност на Middleware
Middleware функциите са от съществено значение за обработката на общи проблеми в Express.js приложения. Осигуряването на типизована безопасност в middleware е също толкова важно, колкото и в обработващите маршрути.
Типизиране на Middleware функции
Основната структура на middleware функция в TypeScript е подобна на тази на обработващ маршрут:
import express, { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Логика за удостоверяване
const isAuthenticated = true; // Заменете с действителна проверка за удостоверяване
if (isAuthenticated) {
next(); // Продължи към следващото middleware или обработващ маршрут
} 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}`);
});
В този пример:
NextFunctionе тип, предоставен от Express.js, който представлява следващата middleware функция във веригата.- Middleware функцията приема същите обекти
RequestиResponseкато обработващите маршрути.
Разширяване на обекта Request
Понякога може да искате да добавите персонализирани свойства към обекта Request във вашия middleware. Например, middleware за удостоверяване може да добави свойство user към обекта на заявката. За да направите това по типизиран начин, трябва да разширите интерфейса Request.
import express, { Request, Response, NextFunction } from 'express';
interface User {
id: string;
username: string;
email: string;
}
// Разширете интерфейса Request
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Логика за удостоверяване (заменете с действителна проверка за удостоверяване)
const user: User = { id: '123', username: 'johndoe', email: 'john.doe@example.com' };
req.user = user; // Добавете потребителя към обекта на заявката
next(); // Продължи към следващото middleware или обработващ маршрут
}
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}`);
});
В този пример:
- Използваме глобална декларация за разширяване на интерфейса
Express.Request. - Добавяме незадължително свойство
userот типUserкъм интерфейсаRequest. - Сега можете да получите достъп до свойството
req.userвъв вашите обработващи маршрути, без TypeScript да ви кара грешка. `?` в `req.user?.username` е от решаващо значение за справяне със случаи, когато потребителят не е удостоверен, предотвратявайки потенциални грешки.
Най-добри практики за TypeScript Express Интеграция
За да увеличите максимално ползите от TypeScript във вашите Express.js приложения, следвайте тези най-добри практики:
- Активирайте Strict Mode: Използвайте опцията
"strict": trueвъв вашия файлtsconfig.json, за да активирате всички опции за строга типизована проверка. Това помага за улавяне на потенциални грешки рано и осигурява по-високо ниво на типизована безопасност. - Използвайте интерфейси и типови алиаси: Дефинирайте интерфейси и типови алиаси, за да представите структурата на вашите данни. Това прави кода ви по-четлив и поддържаем.
- Използвайте генерични типове: Използвайте генерични типове, за да създавате повторно използваеми и типизирани компоненти.
- Пишете unit тестове: Пишете unit тестове, за да проверите коректността на кода си и да се уверите, че типовите ви анотации са точни. Тестването е от решаващо значение за поддържане на качеството на кода.
- Използвайте Linter и Formatter: Използвайте linter (като ESLint) и formatter (като Prettier), за да наложите последователни стилове на кодиране и да улавяте потенциални грешки.
- Избягвайте типа
any: Минимизирайте използването на типаany, тъй като той заобикаля типизованата проверка и обезсмисля целта на използването на TypeScript. Използвайте го само когато е абсолютно необходимо и обмислете използването на по-специфични типове или генерични типове винаги, когато е възможно. - Структурирайте проекта си логично: Организирайте проекта си в модули или папки въз основа на функционалност. Това ще подобри поддръжката и мащабируемостта на вашето приложение.
- Използвайте Dependency Injection: Обмислете използването на контейнер за Dependency Injection за управление на зависимостите на вашето приложение. Това може да направи кода ви по-лесен за тестване и поддръжка. Библиотеки като InversifyJS са популярни избори.
Разширени TypeScript Концепции за Express.js
Използване на Декоратори
Декораторите предоставят сбит и изразителен начин за добавяне на метаданни към класове и функции. Можете да използвате декоратори, за да опростите регистрацията на маршрути в Express.js.
Първо, трябва да активирате експериментални декоратори във вашия файл tsconfig.json, като добавите "experimentalDecorators": true към compilerOptions.
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true
}
}
След това можете да създадете персонализиран декоратор за регистриране на маршрути:
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}`);
});
В този пример:
routeдекораторът приема HTTP метода и пътя като аргументи.- Той регистрира декорирания метод като обработващ маршрут на рутера, свързан с класа.
- Това опростява регистрацията на маршрути и прави кода ви по-четлив.
Използване на персонализирани Type Guards
Type guards са функции, които стесняват типа на променлива в определена област. Можете да използвате персонализирани type guards, за да валидирате тела на заявки или параметри на заявки.
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}`);
});
В този пример:
isProductфункцията е персонализиран type guard, който проверява дали обектът съответства на интерфейсаProduct.- В рамките на обработващия маршрут
/products, функциятаisProductсе използва за валидиране на тялото на заявката. - Ако тялото на заявката е валиден продукт, TypeScript знае, че
req.bodyе от типProductв рамките на блокаif.
Адресиране на Глобални Съображения при API Дизайн
При проектиране на API за глобална аудитория, трябва да се вземат предвид няколко фактора, за да се гарантира достъпност, използваемост и културна чувствителност.
- Локализация и Интернационализация (i18n и L10n):
- Преговори на съдържанието: Поддръжка на множество езици и региони чрез преговори на съдържанието, базирани на хедъра
Accept-Language. - Форматиране на дата и час: Използвайте ISO 8601 формат за представяне на дата и час, за да избегнете двусмислие в различни региони.
- Форматиране на числа: Обработвайте форматирането на числа съгласно локала на потребителя (напр. десетични разделители и разделители на хиляди).
- Обработка на валути: Поддръжка на множество валути и предоставяне на информация за обменния курс, когато е необходимо.
- Посока на текста: Приспособяване към езици отдясно наляво (RTL) като арабски и иврит.
- Преговори на съдържанието: Поддръжка на множество езици и региони чрез преговори на съдържанието, базирани на хедъра
- Часови зони:
- Съхранявайте дати и часове в UTC (Координирано универсално време) от страна на сървъра.
- Позволете на потребителите да посочват предпочитанията си за часова зона и конвертирайте датите и часовете съответно от страна на клиента.
- Използвайте библиотеки като
moment-timezoneза обработка на преобразуване на часови зони.
- Кодиране на символи:
- Използвайте UTF-8 кодиране за всички текстови данни, за да поддържате широк спектър от символи от различни езици.
- Уверете се, че вашата база данни и други системи за съхранение на данни са конфигурирани да използват UTF-8.
- Достъпност:
- Следвайте указанията за достъпност (напр. WCAG), за да направите вашето API достъпно за потребители с увреждания.
- Предоставяйте ясни и описателни съобщения за грешки, които са лесни за разбиране.
- Използвайте семантични HTML елементи и ARIA атрибути в документацията на вашето API.
- Културна чувствителност:
- Избягвайте използването на културно специфични препратки, идиоми или хумор, които може да не бъдат разбрани от всички потребители.
- Имайте предвид културните различия в стиловете на комуникация и предпочитанията.
- Обмислете потенциалното въздействие на вашето API върху различни културни групи и избягвайте увековечаването на стереотипи или пристрастия.
- Поверителност и сигурност на данните:
- Съобразете се с разпоредбите за поверителност на данните като GDPR (Общ регламент за защита на данните) и CCPA (Закон за поверителността на потребителите в Калифорния).
- Внедрете силни механизми за удостоверяване и оторизация за защита на потребителските данни.
- Криптирайте чувствителни данни както при предаване, така и при съхранение.
- Предоставете на потребителите контрол върху техните данни и им позволете да достъпват, променят и изтриват своите данни.
- API Документация:
- Предоставете изчерпателна и добре организирана API документация, която е лесна за разбиране и навигация.
- Използвайте инструменти като Swagger/OpenAPI за генериране на интерактивна API документация.
- Включете примери за код на множество програмни езици, за да задоволите разнообразна аудитория.
- Преведете API документацията си на множество езици, за да достигнете до по-широка аудитория.
- Обработка на грешки:
- Предоставяйте специфични и информативни съобщения за грешки. Избягвайте общи съобщения за грешки като "Възникна проблем".
- Използвайте стандартни HTTP статус кодове, за да посочите типа на грешката (напр. 400 за Bad Request, 401 за Unauthorized, 500 за Internal Server Error).
- Включете кодове или идентификатори за грешки, които могат да се използват за проследяване и отстраняване на проблеми.
- Записвайте грешките от страна на сървъра за отстраняване на грешки и наблюдение.
- Ограничаване на честотата на заявките: Внедрете ограничаване на честотата на заявките, за да защитите вашето API от злоупотреби и да осигурите справедливо използване.
- Версиониране: Използвайте версиониране на API, за да позволите обратно съвместими промени и да избегнете счупване на съществуващи клиенти.
Заключение
Интеграцията на TypeScript с Express значително подобрява надеждността и поддръжката на вашите бекенд API-та. Чрез използването на типизована безопасност в обработващите маршрути и middleware, можете да улавяте грешки рано в процеса на разработка и да изграждате по-здрави и мащабируеми приложения за глобална аудитория. Като дефинирате типове за заявки и отговори, вие гарантирате, че вашето API се придържа към последователна структура на данните, намалявайки вероятността от грешки по време на изпълнение. Не забравяйте да спазвате най-добрите практики като активиране на strict mode, използване на интерфейси и типови алиаси и писане на unit тестове, за да увеличите максимално ползите от TypeScript. Винаги обмисляйте глобални фактори като локализация, часови зони и културна чувствителност, за да гарантирате, че вашите API са достъпни и използваеми в световен мащаб.