FörbÀttra dina Express.js-applikationer med robust typsÀkerhet med hjÀlp av TypeScript. Denna guide tÀcker definitioner för route handlers, typsÀttning av middleware och bÀsta praxis för att bygga skalbara och underhÄllbara API:er.
TypeScript Express-integration: TypsÀkerhet för Route Handlers
TypeScript har blivit en hörnsten i modern JavaScript-utveckling och erbjuder statiska typningsmöjligheter som förbÀttrar kodkvalitet, underhÄllbarhet och skalbarhet. NÀr det kombineras med Express.js, ett populÀrt webbapplikationsramverk för Node.js, kan TypeScript avsevÀrt förbÀttra robustheten i dina backend-API:er. Denna omfattande guide utforskar hur man utnyttjar TypeScript för att uppnÄ typsÀkerhet för route handlers i Express.js-applikationer, och ger praktiska exempel och bÀsta praxis för att bygga robusta och underhÄllbara API:er för en global publik.
Varför typsÀkerhet Àr viktigt i Express.js
I dynamiska sprÄk som JavaScript upptÀcks fel ofta vid körning, vilket kan leda till ovÀntat beteende och svÄrfelsökta problem. TypeScript adresserar detta genom att introducera statisk typning, vilket gör att du kan fÄnga fel under utvecklingen innan de nÄr produktion. I kontexten av Express.js Àr typsÀkerhet sÀrskilt avgörande för route handlers, dÀr du hanterar request- och response-objekt, query-parametrar och request bodies. Felaktig hantering av dessa element kan leda till applikationskrascher, datakorruption och sÀkerhetssÄrbarheter.
- Tidig felupptÀckt: FÄnga typrelaterade fel under utvecklingen, vilket minskar sannolikheten för överraskningar vid körning.
- FörbÀttrad kodunderhÄllbarhet: Typannoteringar gör koden lÀttare att förstÄ och refaktorera.
- FörbÀttrad kodkomplettering och verktyg: IDE:er kan ge bÀttre förslag och felkontroll med typinformation.
- Minskade buggar: TypsÀkerhet hjÀlper till att förhindra vanliga programmeringsfel, som att skicka felaktiga datatyper till funktioner.
SĂ€tta upp ett TypeScript Express.js-projekt
Innan vi dyker in i typsÀkerhet för route handlers, lÄt oss sÀtta upp ett grundlÀggande TypeScript Express.js-projekt. Detta kommer att fungera som grund för vÄra exempel.
FörutsÀttningar
- Node.js och npm (Node Package Manager) installerat. Du kan ladda ner dem frÄn den officiella Node.js-webbplatsen. Se till att du har en ny version för optimal kompatibilitet.
- En kodredigerare som Visual Studio Code, som erbjuder utmÀrkt TypeScript-stöd.
Projektinitialisering
- Skapa en ny projektkatalog:
mkdir typescript-express-app && cd typescript-express-app - Initiera ett nytt npm-projekt:
npm init -y - Installera TypeScript och Express.js:
npm install typescript express - Installera TypeScript-deklarationsfiler för Express.js (viktigt för typsÀkerhet):
npm install @types/express @types/node - Initiera TypeScript:
npx tsc --init(Detta skapar entsconfig.json-fil som konfigurerar TypeScript-kompilatorn.)
Konfigurera TypeScript
Ăppna filen tsconfig.json och konfigurera den pĂ„ lĂ€mpligt sĂ€tt. HĂ€r Ă€r en exempelkonfiguration:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Viktiga konfigurationer att notera:
target: Specificerar ECMAScript-mÄlversionen.es6Àr en bra utgÄngspunkt.module: Specificerar modulens kodgenerering.commonjsÀr ett vanligt val för Node.js.outDir: Specificerar utdatakatalogen för kompilerade JavaScript-filer.rootDir: Specificerar rotkatalogen för dina TypeScript-kÀllfiler.strict: Aktiverar alla strikta typkontrollalternativ för förbÀttrad typsÀkerhet. Detta rekommenderas starkt.esModuleInterop: Möjliggör interoperabilitet mellan CommonJS och ES-moduler.
Skapa startpunkten
Skapa en src-katalog och lÀgg till en index.ts-fil:
mkdir src
touch src/index.ts
Fyll src/index.ts med en grundlÀggande Express.js-server-setup:
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}`);
});
LĂ€gga till ett bygg-skript
LÀgg till ett bygg-skript i din package.json-fil för att kompilera TypeScript-koden:
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "npm run build && npm run start"
}
Nu kan du köra npm run dev för att bygga och starta servern.
TypsÀkerhet för Route Handlers: Definiera Request- och Response-typer
KÀrnan i typsÀkerhet för route handlers ligger i att korrekt definiera typerna för Request- och Response-objekten. Express.js tillhandahÄller generiska typer för dessa objekt som lÄter dig specificera typerna för query-parametrar, request body och route-parametrar.
GrundlÀggande typsÀttning för Route Handlers
LÄt oss börja med en enkel route handler som förvÀntar sig ett namn som en query-parameter:
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}`);
});
I detta exempel:
Request<any, any, any, NameQuery>definierar typen för request-objektet.- Den första
anyrepresenterar route-parametrar (t.ex./users/:id). - Den andra
anyrepresenterar typen för response body. - Den tredje
anyrepresenterar typen för request body. NameQueryÀr ett interface som definierar strukturen för query-parametrarna.
Genom att definiera interfacet NameQuery kan TypeScript nu verifiera att egenskapen req.query.name finns och Àr av typen string. Om du försöker komma Ät en egenskap som inte finns eller tilldelar ett vÀrde av fel typ, kommer TypeScript att flagga ett fel.
Hantera Request Bodies
För routes som accepterar request bodies (t.ex. POST, PUT, PATCH) kan du definiera ett interface för request body och anvÀnda det i Request-typen:
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
const app = express();
const port = 3000;
app.use(bodyParser.json()); // Viktigt för att tolka JSON request bodies
interface CreateUserRequest {
firstName: string;
lastName: string;
email: string;
}
app.post('/users', (req: Request, res: Response) => {
const { firstName, lastName, email } = req.body;
// Validera request body
if (!firstName || !lastName || !email) {
return res.status(400).send('Missing required fields.');
}
// Bearbeta skapandet av anvÀndaren (t.ex. spara till databas)
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}`);
});
I detta exempel:
CreateUserRequestdefinierar strukturen för den förvÀntade request body.app.use(bodyParser.json())Àr avgörande för att tolka JSON request bodies. Utan det kommerreq.bodyatt vara undefined.Request-typen Àr nuRequest<any, any, CreateUserRequest>, vilket indikerar att request body ska följaCreateUserRequest-interfacet.
TypeScript kommer nu att sÀkerstÀlla att req.body-objektet innehÄller de förvÀntade egenskaperna (firstName, lastName och email) och att deras typer Àr korrekta. Detta minskar avsevÀrt risken för körningsfel orsakade av felaktig data i request body.
Hantera Route-parametrar
För routes med parametrar (t.ex. /users/:id) kan du definiera ett interface för route-parametrarna och anvÀnda det i Request-typen:
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}`);
});
I detta exempel:
UserParamsdefinierar strukturen för route-parametrarna, och specificerar attid-parametern ska vara en strÀng.Request-typen Àr nuRequest<UserParams>, vilket indikerar attreq.params-objektet ska följaUserParams-interfacet.
TypeScript kommer nu att sÀkerstÀlla att egenskapen req.params.id finns och Àr av typen string. Detta hjÀlper till att förhindra fel orsakade av att komma Ät route-parametrar som inte finns eller anvÀnda dem med felaktiga typer.
Specificera Response-typer
Ăven om fokus pĂ„ typsĂ€kerhet för requests Ă€r avgörande, förbĂ€ttrar Ă€ven definitionen av response-typer kodens tydlighet och hjĂ€lper till att förhindra inkonsekvenser. Du kan definiera typen av data du skickar tillbaka i svaret.
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}`);
});
HÀr specificerar Response<User[]> att response body ska vara en array av User-objekt. Detta hjÀlper till att sÀkerstÀlla att du konsekvent skickar rÀtt datastruktur i dina API-svar. Om du försöker skicka data som inte överensstÀmmer med typen `User[]`, kommer TypeScript att utfÀrda en varning.
TypsÀkerhet för Middleware
Middleware-funktioner Àr avgörande för att hantera övergripande aspekter i Express.js-applikationer. Att sÀkerstÀlla typsÀkerhet i middleware Àr lika viktigt som i route handlers.
TypsÀttning av Middleware-funktioner
Grundstrukturen för en middleware-funktion i TypeScript liknar den för en route handler:
import express, { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Autentiseringslogik
const isAuthenticated = true; // ErsÀtt med faktisk autentiseringskontroll
if (isAuthenticated) {
next(); // GÄ vidare till nÀsta middleware eller 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}`);
});
I detta exempel:
NextFunctionÀr en typ som tillhandahÄlls av Express.js och representerar nÀsta middleware-funktion i kedjan.- Middleware-funktionen tar emot samma
Request- ochResponse-objekt som route handlers.
Utöka Request-objektet
Ibland kanske du vill lÀgga till anpassade egenskaper i Request-objektet i din middleware. Till exempel kan en autentiserings-middleware lÀgga till en user-egenskap i request-objektet. För att göra detta pÄ ett typsÀkert sÀtt mÄste du utöka Request-interfacet.
import express, { Request, Response, NextFunction } from 'express';
interface User {
id: string;
username: string;
email: string;
}
// Utöka Request-interfacet
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Autentiseringslogik (ersÀtt med faktisk autentiseringskontroll)
const user: User = { id: '123', username: 'johndoe', email: 'john.doe@example.com' };
req.user = user; // LÀgg till anvÀndaren i request-objektet
next(); // GÄ vidare till nÀsta middleware eller 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}`);
});
I detta exempel:
- Vi anvÀnder en global deklaration för att utöka
Express.Request-interfacet. - Vi lÀgger till en valfri
user-egenskap av typenUseriRequest-interfacet. - Nu kan du komma Ät egenskapen
req.useri dina route handlers utan att TypeScript klagar. `?` i `req.user?.username` Àr avgörande för att hantera fall dÀr anvÀndaren inte Àr autentiserad, vilket förhindrar potentiella fel.
BÀsta praxis för TypeScript Express-integration
För att maximera fördelarna med TypeScript i dina Express.js-applikationer, följ dessa bÀsta praxis:
- Aktivera Strict Mode: AnvÀnd alternativet
"strict": truei dintsconfig.json-fil för att aktivera alla strikta typkontrollalternativ. Detta hjÀlper till att fÄnga potentiella fel tidigt och sÀkerstÀller en högre nivÄ av typsÀkerhet. - AnvÀnd Interfaces och Type Aliases: Definiera interfaces och type aliases för att representera strukturen pÄ din data. Detta gör din kod mer lÀsbar och underhÄllbar.
- AnvÀnd Generiska Typer: Utnyttja generiska typer för att skapa ÄteranvÀndbara och typsÀkra komponenter.
- Skriv Enhetstester: Skriv enhetstester för att verifiera korrektheten i din kod och sÀkerstÀlla att dina typannoteringar Àr korrekta. Testning Àr avgörande för att upprÀtthÄlla kodkvalitet.
- AnvÀnd en Linter och Formatter: AnvÀnd en linter (som ESLint) och en formatter (som Prettier) för att upprÀtthÄlla konsekventa kodstilar och fÄnga potentiella fel.
- Undvik
any-typen: Minimera anvÀndningen avany-typen, eftersom den kringgÄr typkontroll och motverkar syftet med att anvÀnda TypeScript. AnvÀnd den endast nÀr det Àr absolut nödvÀndigt och övervÀg att anvÀnda mer specifika typer eller generiska typer nÀr det Àr möjligt. - Strukturera ditt projekt logiskt: Organisera ditt projekt i moduler eller mappar baserat pÄ funktionalitet. Detta kommer att förbÀttra underhÄllbarheten och skalbarheten i din applikation.
- AnvĂ€nd Dependency Injection: ĂvervĂ€g att anvĂ€nda en dependency injection-container för att hantera din applikations beroenden. Detta kan göra din kod mer testbar och underhĂ„llbar. Bibliotek som InversifyJS Ă€r populĂ€ra val.
Avancerade TypeScript-koncept för Express.js
AnvÀnda Decorators
Decorators erbjuder ett koncist och uttrycksfullt sÀtt att lÀgga till metadata till klasser och funktioner. Du kan anvÀnda decorators för att förenkla registreringen av routes i Express.js.
Först mÄste du aktivera experimentella decorators i din tsconfig.json-fil genom att lÀgga till "experimentalDecorators": true till compilerOptions.
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true
}
}
Sedan kan du skapa en anpassad decorator för att registrera routes:
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}`);
});
I detta exempel:
route-decoratorn tar HTTP-metoden och sökvÀgen som argument.- Den registrerar den dekorerade metoden som en route handler pÄ den router som Àr associerad med klassen.
- Detta förenklar registreringen av routes och gör din kod mer lÀsbar.
AnvÀnda anpassade Type Guards
Type guards Àr funktioner som begrÀnsar typen av en variabel inom ett specifikt scope. Du kan anvÀnda anpassade type guards för att validera request bodies eller query-parametrar.
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}`);
});
I detta exempel:
- Funktionen
isProductÀr en anpassad type guard som kontrollerar om ett objekt överensstÀmmer medProduct-interfacet. - Inuti
/products-route-handlern anvÀnds funktionenisProductför att validera request body. - Om request body Àr en giltig produkt, vet TypeScript att
req.bodyÀr av typenProductinomif-blocket.
Att hantera globala aspekter i API-design
NÀr man designar API:er för en global publik bör flera faktorer beaktas för att sÀkerstÀlla tillgÀnglighet, anvÀndbarhet och kulturell kÀnslighet.
- Lokalisering och internationalisering (i18n och L10n):
- Content Negotiation: Stöd flera sprÄk och regioner genom content negotiation baserat pÄ
Accept-Language-headern. - Datum- och tidsformatering: AnvÀnd ISO 8601-format för datum- och tidsrepresentation för att undvika tvetydighet mellan olika regioner.
- Talformatering: Hantera talformatering enligt anvÀndarens locale (t.ex. decimal- och tusentalsavgrÀnsare).
- Valutahantering: Stöd flera valutor och tillhandahÄll information om vÀxelkurser dÀr det behövs.
- Textriktning: Ta hÀnsyn till höger-till-vÀnster-sprÄk (RTL) som arabiska och hebreiska.
- Content Negotiation: Stöd flera sprÄk och regioner genom content negotiation baserat pÄ
- Tidszoner:
- Lagra datum och tider i UTC (Coordinated Universal Time) pÄ serversidan.
- LÄt anvÀndare specificera sin föredragna tidszon och konvertera datum och tider dÀrefter pÄ klientsidan.
- AnvÀnd bibliotek som
moment-timezoneför att hantera tidszonskonverteringar.
- Teckenkodning:
- AnvÀnd UTF-8-kodning för all textdata för att stödja ett brett utbud av tecken frÄn olika sprÄk.
- Se till att din databas och andra datalagringssystem Àr konfigurerade att anvÀnda UTF-8.
- TillgÀnglighet:
- Följ riktlinjer för tillgÀnglighet (t.ex. WCAG) för att göra ditt API tillgÀngligt för anvÀndare med funktionsnedsÀttningar.
- Ge tydliga och beskrivande felmeddelanden som Àr lÀtta att förstÄ.
- AnvÀnd semantiska HTML-element och ARIA-attribut i din API-dokumentation.
- Kulturell kÀnslighet:
- Undvik att anvÀnda kulturspecifika referenser, idiom eller humor som kanske inte förstÄs av alla anvÀndare.
- Var medveten om kulturella skillnader i kommunikationsstilar och preferenser.
- TÀnk pÄ den potentiella inverkan ditt API kan ha pÄ olika kulturella grupper och undvik att vidmakthÄlla stereotyper eller fördomar.
- Dataskydd och sÀkerhet:
- Följ dataskyddsförordningar som GDPR (General Data Protection Regulation) och CCPA (California Consumer Privacy Act).
- Implementera starka autentiserings- och auktoriseringsmekanismer för att skydda anvÀndardata.
- Kryptera kÀnslig data bÄde under överföring och i vila.
- Ge anvÀndare kontroll över sin data och tillÄt dem att komma Ät, Àndra och radera sin data.
- API-dokumentation:
- TillhandahÄll omfattande och vÀlorganiserad API-dokumentation som Àr lÀtt att förstÄ och navigera i.
- AnvÀnd verktyg som Swagger/OpenAPI för att generera interaktiv API-dokumentation.
- Inkludera kodexempel pÄ flera programmeringssprÄk för att tillgodose en mÄngsidig publik.
- ĂversĂ€tt din API-dokumentation till flera sprĂ„k för att nĂ„ en bredare publik.
- Felhantering:
- Ge specifika och informativa felmeddelanden. Undvik generiska felmeddelanden som "NÄgot gick fel.".
- AnvÀnd standard HTTP-statuskoder för att indikera typen av fel (t.ex. 400 for Bad Request, 401 for Unauthorized, 500 for Internal Server Error).
- Inkludera felkoder eller identifierare som kan anvÀndas för att spÄra och felsöka problem.
- Logga fel pÄ serversidan för felsökning och övervakning.
- HastighetsbegrÀnsning: Implementera hastighetsbegrÀnsning för att skydda ditt API frÄn missbruk och sÀkerstÀlla rÀttvis anvÀndning.
- Versionering: AnvÀnd API-versionering för att tillÄta bakÄtkompatibla Àndringar och undvika att befintliga klienter slutar fungera.
Slutsats
TypeScript Express-integration förbÀttrar avsevÀrt tillförlitligheten och underhÄllbarheten i dina backend-API:er. Genom att utnyttja typsÀkerhet i route handlers och middleware kan du fÄnga fel tidigt i utvecklingsprocessen och bygga mer robusta och skalbara applikationer för en global publik. Genom att definiera request- och response-typer sÀkerstÀller du att ditt API följer en konsekvent datastruktur, vilket minskar sannolikheten för körningsfel. Kom ihÄg att följa bÀsta praxis som att aktivera strict mode, anvÀnda interfaces och type aliases, och skriva enhetstester för att maximera fördelarna med TypeScript. TÀnk alltid pÄ globala faktorer som lokalisering, tidszoner och kulturell kÀnslighet för att sÀkerstÀlla att dina API:er Àr tillgÀngliga och anvÀndbara över hela vÀrlden.