Mestre TypeScript-validering under kjøretid. Lær om ledende biblioteker, beste praksis og eksempler for å bygge robuste og pålitelige applikasjoner globalt.
TypeScript-validering: Mestring av kjøretids-typesjekkingsbiblioteker for robuste applikasjoner
Ettersom applikasjoner vokser i kompleksitet og distribueres på tvers av ulike globale landskap, blir det avgjørende å sikre dataintegritet og forhindre uventede feil. Mens TypeScript utmerker seg med typesjekking under kompilering, og fanger feil før koden din i det hele tatt kjører, finnes det scenarier hvor kjøretidsvalidering er uunnværlig. Dette gjelder spesielt når man arbeider med eksterne datakilder som API-forespørsler, brukerinput eller konfigurasjonsfiler, hvor formen og typene av data ikke er garantert.
Denne omfattende guiden dykker ned i det kritiske området TypeScript-validering under kjøretid. Vi vil utforske hvorfor det er nødvendig, introdusere ledende biblioteker som gir utviklere mulighet til å implementere robuste valideringsstrategier, og gi praktiske eksempler for å hjelpe deg med å bygge mer motstandsdyktige applikasjoner for din internasjonale brukerbase.
Hvorfor kjøretids-typesjekking er avgjørende i TypeScript
TypeScripts statiske typing er et kraftig verktøy. Det lar oss definere forventede datastrukturer og -typer, og kompilatoren vil flagge avvik under utvikling. Imidlertid slettes TypeScripts typeinformasjon hovedsakelig under kompileringsprosessen til JavaScript. Dette betyr at når koden din kjører, har JavaScript-motoren ingen iboende kunnskap om TypeScript-typene du definerte.
Vurder disse scenariene hvor kjøretidsvalidering blir essensielt:
- API-responser: Data mottatt fra eksterne API-er, selv de med dokumenterte skjemaer, kan av og til avvike fra forventningene på grunn av uforutsette problemer, endringer i API-leverandørens implementasjon eller nettverksfeil.
- Brukerinput: Skjemaer og brukergrensesnitt samler inn data som må valideres før behandling, for å sikre at bare gyldige og forventede formater aksepteres. Dette er avgjørende for internasjonale applikasjoner hvor inputformater (som telefonnumre eller datoer) kan variere betydelig.
- Konfigurasjonsfiler: Applikasjoner er ofte avhengige av konfigurasjonsfiler (f.eks. JSON, YAML). Validering av disse filene ved oppstart sikrer at applikasjonen er riktig konfigurert, noe som forhindrer krasj eller feil oppførsel.
- Data fra upålitelige kilder: Når man samhandler med data som stammer fra potensielt upålitelige kilder, er grundig validering et sikkerhetstiltak for å forhindre injeksjonsangrep eller datakorrupsjon.
- Konsistens på tvers av miljøer: Det er viktig å sikre at datastrukturer forblir konsistente på tvers av forskjellige JavaScript-kjøretider (Node.js, nettlesere) og under serialisering/deserialisering (f.eks. JSON.parse/stringify) er vital.
Uten kjøretidsvalidering kan applikasjonen din møte uventede data, noe som fører til kjøretidsfeil, datakorrupsjon, sikkerhetssårbarheter og en dårlig brukeropplevelse. Dette er spesielt problematisk i en global kontekst, hvor data kan stamme fra forskjellige systemer og følge ulike regionale standarder.
Nøkkelbiblioteker for TypeScript kjøretidsvalidering
Heldigvis tilbyr TypeScript-økosystemet flere utmerkede biblioteker spesielt designet for kjøretids-typesjekking og datavalidering. Disse bibliotekene lar deg definere skjemaer som beskriver dine forventede datastrukturer, og deretter bruke disse skjemaene til å validere innkommende data.
Vi vil utforske noen av de mest populære og effektive bibliotekene:
1. Zod
Zod har raskt vunnet popularitet for sitt intuitive API, sterke TypeScript-integrasjon og omfattende funksjonssett. Det lar deg definere et "skjema" for dataene dine, og deretter bruke dette skjemaet til å parse og validere data under kjøretid. Zods skjemaer er sterkt typede, noe som betyr at TypeScript-typene kan utledes direkte fra skjemadefinisjonen, noe som minimerer behovet for manuelle typeannotasjoner.
Nøkkelfunksjoner i Zod:
- Inferensial typing: Utled TypeScript-typer direkte fra Zod-skjemaer.
- Deklarativ skjemadefinisjon: Definer komplekse datastrukturer, inkludert nestede objekter, arrayer, unioner, snitt og egendefinerte typer, på en klar og lesbar måte.
- Kraftig transformasjon: Transformer data under parsing (f.eks. streng til tall, dato-parsing).
- Omfattende feilrapportering: Gir detaljerte og brukervennlige feilmeldinger, avgjørende for feilsøking og for å gi tilbakemelding til brukere globalt.
- Innebygde validatorer: Tilbyr et bredt spekter av innebygde validatorer for strenger, tall, booleans, datoer og mer, sammen med muligheten til å lage egendefinerte validatorer.
- Kjedenes API: Skjemaer er enkle å sette sammen og utvide.
Eksempel: Validering av en brukerprofil med Zod
La oss forestille oss at vi mottar brukerprofildata fra et API. Vi ønsker å sikre at brukeren har et gyldig navn, en valgfri alder og en liste over interesser.
import { z } from 'zod';
// Define the schema for a User Profile
const UserProfileSchema = z.object({
name: z.string().min(1, "Name cannot be empty."), // Name is a required string, at least 1 character
age: z.number().int().positive().optional(), // Age is an optional positive integer
interests: z.array(z.string()).min(1, "At least one interest is required."), // Interests is an array of strings, at least one item
isActive: z.boolean().default(true) // isActive is a boolean, defaults to true if not provided
});
// Infer the TypeScript type from the schema
type UserProfile = z.infer<typeof UserProfileSchema>;
// Example API response data
const apiResponse1 = {
name: "Alice",
age: 30,
interests: ["coding", "travel"],
isActive: false
};
const apiResponse2 = {
name: "Bob",
// age is missing
interests: [] // empty interests array
};
// --- Validation Example 1 ---
try {
const validatedProfile1 = UserProfileSchema.parse(apiResponse1);
console.log('Profile 1 is valid:', validatedProfile1);
// TypeScript now knows validatedProfile1 has the type UserProfile
} catch (error) {
if (error instanceof z.ZodError) {
console.error('Validation errors for Profile 1:', error.errors);
} else {
console.error('An unexpected error occurred:', error);
}
}
// --- Validation Example 2 ---
try {
const validatedProfile2 = UserProfileSchema.parse(apiResponse2);
console.log('Profile 2 is valid:', validatedProfile2);
} catch (error) {
if (error instanceof z.ZodError) {
console.error('Validation errors for Profile 2:', error.errors);
/*
Expected output for errors:
[
{ code: 'array_min_size', message: 'At least one interest is required.', path: [ 'interests' ] }
]
*/
} else {
console.error('An unexpected error occurred:', error);
}
}
// --- Example with optional property behavior ---
const apiResponse3 = {
name: "Charlie",
interests: ["reading"]
// isActive is omitted, will default to true
};
try {
const validatedProfile3 = UserProfileSchema.parse(apiResponse3);
console.log('Profile 3 is valid (isActive defaults to true):', validatedProfile3);
/*
Expected output: {
name: 'Charlie',
interests: [ 'reading' ],
isActive: true
}
*/
} catch (error) {
console.error('Validation errors for Profile 3:', error);
}
Zods feilrapportering er spesielt nyttig for internasjonale applikasjoner, da du kan internasjonalisere feilmeldingene selv basert på brukerens språkinnstillinger, selv om biblioteket i seg selv gir strukturerte feildata som gjør denne prosessen enkel.
2. Yup
Yup er et annet svært populært og modent valideringsbibliotek for JavaScript og TypeScript. Det brukes ofte med Formik for skjema-validering, men er like kraftig for generell datavalidering. Yup bruker et flytende API for å definere skjemaer, som deretter brukes til å validere JavaScript-objekter.
Nøkkelfunksjoner i Yup:
- Skjemabasert validering: Definer datasjemaer ved hjelp av en kjedenes, deklarativ syntaks.
- Typeinferens: Kan utlede TypeScript-typer, selv om det i noen tilfeller kan kreve mer eksplisitte typedefinisjoner sammenlignet med Zod.
- Rikt sett med validatorer: Støtter validering for ulike datatyper, inkludert strenger, tall, datoer, arrayer, objekter og mer.
- Betinget validering: Tillater valideringsregler som avhenger av verdiene i andre felt.
- Tilpassbare feilmeldinger: Definer enkelt egendefinerte feilmeldinger for valideringsfeil.
- Kryssplattformkompatibilitet: Fungerer sømløst i Node.js- og nettlesermiljøer.
Eksempel: Validering av en produktkatalogoppføring med Yup
La oss validere en produktoppføring, for å sikre at den har et navn, pris og en valgfri beskrivelse.
import * as yup from 'yup';
// Define the schema for a Product Entry
const ProductSchema = yup.object({
name: yup.string().required('Product name is required.'),
price: yup.number().positive('Price must be a positive number.').required('Price is required.'),
description: yup.string().optional('Description is optional.'),
tags: yup.array(yup.string()).default([]), // Default to an empty array if not provided
releaseDate: yup.date().optional()
});
// Infer the TypeScript type from the schema
type Product = yup.InferType<typeof ProductSchema>;
// Example product data
const productData1 = {
name: "Global Gadget",
price: 199.99,
tags: ["electronics", "new arrival"],
releaseDate: new Date('2023-10-27T10:00:00Z')
};
const productData2 = {
name: "Budget Widget",
price: -10.50 // Invalid price
};
// --- Validation Example 1 ---
ProductSchema.validate(productData1, { abortEarly: false })
.then(function (validProduct: Product) {
console.log('Product 1 is valid:', validProduct);
// TypeScript knows validProduct is of type Product
})
.catch(function (err: yup.ValidationError) {
console.error('Validation errors for Product 1:', err.errors);
});
// --- Validation Example 2 ---
ProductSchema.validate(productData2, { abortEarly: false })
.then(function (validProduct: Product) {
console.log('Product 2 is valid:', validProduct);
})
.catch(function (err: yup.ValidationError) {
console.error('Validation errors for Product 2:', err.errors);
/*
Expected output for errors:
[
'Price must be a positive number.'
]
*/
});
// --- Example with default value behavior ---
const productData3 = {
name: "Simple Item",
price: 5.00
// tags and releaseDate are omitted
};
ProductSchema.validate(productData3, { abortEarly: false })
.then(function (validProduct: Product) {
console.log('Product 3 is valid (tags default to []):', validProduct);
/*
Expected output: {
name: 'Simple Item',
price: 5,
tags: [],
releaseDate: undefined
}
*/
})
.catch(function (err: yup.ValidationError) {
console.error('Validation errors for Product 3:', err.errors);
});
Yups omfattende dokumentasjon og store fellesskap gjør det til et pålitelig valg, spesielt for prosjekter med eksisterende Yup-bruk eller de som trenger finmasket kontroll over feilrapportering og komplekse valideringsflyter.
3. io-ts
io-ts er et bibliotek som bringer kjøretids-typevalidering til TypeScript ved å bruke en funksjonell programmeringstilnærming. Det definerer "codecs" som brukes til å kode og dekode data, og sikrer at data samsvarer med en spesifikk type under kjøretid. Dette biblioteket er kjent for sin stringens og sterke overholdelse av funksjonelle prinsipper.
Nøkkelfunksjoner i io-ts:
- Codec-basert: Bruker kodeker for å definere og validere typer.
- Funksjonell programmeringsparadigma: Passer godt med funksjonelle programmeringsstiler.
- Kjøretids-typesikkerhet: Gir garantert typesikkerhet under kjøretid.
- Utvidbar: Tillater opprettelse av egendefinerte kodeker.
- Omfattende funksjonssett: Støtter uniontyper, snitt-typer, rekursive typer og mer.
- Følgebiblioteker: Har følgebiblioteker som
io-ts-promisefor enklere promise-integrasjon ogio-ts-reportersfor bedre feilrapportering.
Eksempel: Validering av et geolokasjonspunkt med io-ts
Validering av geografiske koordinater er en vanlig oppgave, spesielt for lokasjonsbevisste globale applikasjoner.
import * as t from 'io-ts';
import { formatValidationErrors } from 'io-ts-reporters'; // For better error reporting
// Define the codec for a Geolocation Point
const GeolocationPoint = t.type({
latitude: t.number,
longitude: t.number,
accuracy: t.union([t.number, t.undefined]) // accuracy is optional
});
// Infer the TypeScript type from the codec
type Geolocation = t.TypeOf<typeof GeolocationPoint>;
// Example geolocation data
const geoData1 = {
latitude: 34.0522,
longitude: -118.2437,
accuracy: 10.5
};
const geoData2 = {
latitude: 'not a number',
longitude: -0.1278
};
// --- Validation Example 1 ---
const result1 = GeolocationPoint.decode(geoData1);
if (result1._tag === 'Right') {
const validatedGeo1: Geolocation = result1.right;
console.log('Geolocation 1 is valid:', validatedGeo1);
} else {
// result1._tag === 'Left'
console.error('Validation errors for Geolocation 1:', formatValidationErrors(result1.left));
}
// --- Validation Example 2 ---
const result2 = GeolocationPoint.decode(geoData2);
if (result2._tag === 'Right') {
const validatedGeo2: Geolocation = result2.right;
console.log('Geolocation 2 is valid:', validatedGeo2);
} else {
// result2._tag === 'Left'
console.error('Validation errors for Geolocation 2:', formatValidationErrors(result2.left));
/*
Expected output for errors:
- latitude: Expected number but received String
*/
}
// --- Example with optional property behavior ---
const geoData3 = {
latitude: 51.5074, // London
longitude: -0.1278
// accuracy is omitted
};
const result3 = GeolocationPoint.decode(geoData3);
if (result3._tag === 'Right') {
const validatedGeo3: Geolocation = result3.right;
console.log('Geolocation 3 is valid (accuracy is undefined):', validatedGeo3);
/*
Expected output: {
latitude: 51.5074,
longitude: -0.1278,
accuracy: undefined
}
*/
}
else {
console.error('Validation errors for Geolocation 3:', formatValidationErrors(result3.left));
}
io-ts er et kraftig valg for prosjekter som omfavner funksjonelle programmeringsprinsipper og krever en høy grad av tillit til kjøretids-typesikkerhet. Dens detaljerte feilrapportering, spesielt når den kombineres med io-ts-reporters, er uvurderlig for feilsøking av internasjonaliserte applikasjoner.
4. class-validator
class-validator og dets ledsager class-transformer er utmerkede for scenarier hvor du arbeider med klasser, spesielt i rammeverk som NestJS. Det lar deg definere valideringsregler ved hjelp av dekoratorer direkte på klasseegenskaper.
Nøkkelfunksjoner i class-validator:
- Dekoratorbasert validering: Bruk dekoratorer (f.eks.
@IsEmail(),@IsNotEmpty()) på klasseegenskaper. - Class-Transformer-integrasjon: Transformer sømløst innkommende data til klasseinstanser før validering.
- Utvidbar: Opprett egendefinerte valideringsdekoratorer.
- Innebygde validatorer: Et bredt utvalg av dekoratorer for vanlige valideringsbehov.
- Feilhåndtering: Gir detaljerte valideringsfeilobjekter.
Eksempel: Validering av et e-postregistreringsskjema med class-validator
Dette er spesielt nyttig for backend-API-er som håndterer brukerregistreringer fra hele verden.
import 'reflect-metadata'; // Required for decorators
import { validate, Contains, IsInt, Length, IsEmail, IsOptional } from 'class-validator';
import { plainToClass, classToPlain } from 'class-transformer';
// Define the DTO (Data Transfer Object) with validation decorators
class UserRegistrationDto {
@Length(5, 50, { message: 'Username must be between 5 and 50 characters.' })
username: string;
@IsEmail({}, { message: 'Invalid email address format.' })
email: string;
@IsInt({ message: 'Age must be an integer.' })
@IsOptional() // Age is optional
age?: number;
constructor(username: string, email: string, age?: number) {
this.username = username;
this.email = email;
this.age = age;
}
}
// Example incoming data (e.g., from an API request body)
const registrationData1 = {
username: "global_user",
email: "user@example.com",
age: 25
};
const registrationData2 = {
username: "short", // Too short username
email: "invalid-email", // Invalid email
age: 30.5 // Not an integer
};
// --- Validation Example 1 ---
// First, transform plain object into a class instance
const userDto1 = plainToClass(UserRegistrationDto, registrationData1);
validate(userDto1).then(errors => {
if (errors.length > 0) {
console.error('Validation errors for Registration 1:', errors);
} else {
console.log('Registration 1 is valid:', classToPlain(userDto1)); // Convert back to plain object for output
}
});
// --- Validation Example 2 ---
const userDto2 = plainToClass(UserRegistrationDto, registrationData2);
validate(userDto2).then(errors => {
if (errors.length > 0) {
console.error('Validation errors for Registration 2:', errors.map(err => err.constraints));
/*
Expected output for errors.constraints:
[ {
length: 'Username must be between 5 and 50 characters.',
isEmail: 'Invalid email address format.',
isInt: 'Age must be an integer.'
} ]
*/
} else {
console.log('Registration 2 is valid:', classToPlain(userDto2));
}
});
// --- Example with optional property behavior ---
const registrationData3 = {
username: "validUser",
email: "valid@example.com"
// age is omitted, which is allowed by @IsOptional()
};
const userDto3 = plainToClass(UserRegistrationDto, registrationData3);
validate(userDto3).then(errors => {
if (errors.length > 0) {
console.error('Validation errors for Registration 3:', errors);
} else {
console.log('Registration 3 is valid (age is undefined):', classToPlain(userDto3));
/*
Expected output: {
username: 'validUser',
email: 'valid@example.com',
age: undefined
}
*/
}
});
class-validator er spesielt effektiv i server-side applikasjoner eller rammeverk som sterkt er avhengige av klasser og objektorientert programmering. Dens dekoratorbaserte syntaks er veldig uttrykksfull og utviklervennlig.
Velge riktig valideringsbibliotek
Det beste valideringsbiblioteket for prosjektet ditt avhenger av flere faktorer:
- Prosjektparadigma: Hvis du er sterkt inne i funksjonell programmering, kan
io-tsvære ditt foretrukne valg. For objektorienterte tilnærminger skinnerclass-validator. For en mer generell, deklarativ tilnærming med utmerket TypeScript-inferens, erZoden sterk kandidat.Yuptilbyr et modent og fleksibelt API som passer for mange scenarier. - TypeScript-integrasjon:
Zodleder an når det gjelder sømløs TypeScript-typeinferens direkte fra skjemaer. Andre tilbyr god integrasjon, men kan kreve mer eksplisitte typedefinisjoner. - Læringskurve:
ZodogYupanses generelt som enklere å komme i gang med for nykommere.io-tshar en brattere læringskurve på grunn av sin funksjonelle natur.class-validatorer enkelt hvis du er komfortabel med dekoratorer. - Økosystem og fellesskap:
YupogZodhar store og aktive fellesskap, som gir rikelig med ressurser og støtte. - Spesifikke funksjoner: Hvis du trenger spesifikke funksjoner som komplekse transformasjoner (
Zod), skjemaintegrasjon (Yup) eller dekoratorbasert validering (class-validator), kan disse påvirke avgjørelsen din.
For mange moderne TypeScript-prosjekter treffer Zod ofte blink på grunn av sin utmerkede typeinferens, intuitive API og kraftige funksjoner. Ikke overse imidlertid styrkene til andre biblioteker.
Beste praksis for kjøretidsvalidering
Å implementere kjøretidsvalidering effektivt krever mer enn bare å velge et bibliotek. Her er noen beste praksis å følge:
1. Valider tidlig, valider ofte
Jo tidligere du validerer data, jo raskere kan du fange feil. Dette prinsippet er ofte oppsummert som "feil raskt". Valider data så snart de kommer inn i systemet ditt, enten det er fra en API-forespørsel, brukerinput eller en konfigurasjonsfil.
2. Sentraliser valideringslogikken
Unngå å spre valideringslogikk gjennom hele kodebasen din. Definer skjemaene eller valideringsreglene dine i dedikerte moduler eller klasser. Dette gjør koden din mer organisert, enklere å vedlikeholde og reduserer duplisering.
3. Bruk beskrivende feilmeldinger
Valideringsfeil bør være informative. For internasjonale applikasjoner betyr dette at feilmeldingene bør være:
- Klar og konsis: Enkel å forstå for brukere uavhengig av deres tekniske bakgrunn.
- Handlingsbar: Veiled brukeren om hvordan de skal korrigere input.
- Lokaliserbar: Design systemet ditt for å tillate oversettelse av feilmeldinger basert på brukerens språkinnstillinger. De strukturerte feilene som leveres av valideringsbibliotekene er nøkkelen til å muliggjøre dette.
For eksempel, i stedet for bare "Ugyldig input", bruk "Vennligst skriv inn en gyldig e-postadresse i formatet eksempel@domene.com." For internasjonale brukere kan dette lokaliseres til deres språk og regionale e-postkonvensjoner.
4. Definer skjemaer som samsvarer med TypeScript-typene dine
Tilby deg konsistens mellom TypeScript-typene dine og kjøretidsvalideringsskjemaene dine. Biblioteker som Zod utmerker seg ved å utlede typer fra skjemaer, noe som er det ideelle scenariet. Hvis du manuelt definerer typer og skjemaer separat, sørg for at de er synkronisert for å unngå avvik.
5. Håndter valideringsfeil elegant
Ikke la valideringsfeil krasje applikasjonen din. Implementer robust feilhåndtering. For API-endepunkter, returner passende HTTP-statuskoder (f.eks. 400 Bad Request) og en strukturert JSON-respons som beskriver feilene. For brukergrensesnitt, vis tydelige feilmeldinger ved siden av de relevante skjema-feltene.
6. Vurder validering i ulike lag
Klient-side validering gir umiddelbar tilbakemelding til brukere, noe som forbedrer brukeropplevelsen. Imidlertid er den ikke sikker, da den kan omgås. Server-side validering er avgjørende for dataintegritet og sikkerhet, da den er den siste forsvarslinjen. Implementer alltid server-side validering, selv om du har klient-side validering.
7. Dra nytte av TypeScripts typeinferens
Bruk biblioteker som gir sterk TypeScript-integrasjon. Dette reduserer kjeleplatekode og sikrer at valideringsskjemaene og TypeScript-typene dine alltid er synkronisert. Når et bibliotek kan utlede typer fra skjemaer (som Zod), er det en betydelig fordel.
8. Globale hensyn: Tidssoner, valutaer og formater
Når du bygger for et globalt publikum, må valideringsregler ta hensyn til regionale forskjeller:
- Datoer og klokkeslett: Valider datoer og klokkeslett i henhold til forventede formater (f.eks. DD/MM/ÅÅÅÅ vs. MM/DD/ÅÅÅÅ) og håndter tidssonekonverteringer korrekt. Biblioteker som Zod har innebygde datoparser som kan konfigureres.
- Valutaer: Valider valutaverdier, potensielt inkludert spesifikke presisjonskrav eller valutakoder.
- Telefonnumre: Implementer robust validering for internasjonale telefonnumre, med tanke på landskoder og varierende formater. Biblioteker som `libphonenumber-js` kan brukes i forbindelse med valideringsskjemaer.
- Adresser: Validering av adressekomponenter kan være kompleks på grunn av betydelige internasjonale variasjoner i struktur og obligatoriske felt.
Valideringsskjemaene dine bør være fleksible nok til å håndtere disse variasjonene eller spesifikke nok for målmarkedene du betjener.
Konklusjon
Mens TypeScripts kompileringstids-sjekking er en hjørnestein i moderne webutvikling, er kjøretids-typesjekking en like viktig komponent for å bygge robuste, sikre og vedlikeholdbare applikasjoner, spesielt i en global kontekst. Ved å utnytte kraftige biblioteker som Zod, Yup, io-ts og class-validator, kan du sikre dataintegritet, forhindre uventede feil og gi en mer pålitelig opplevelse for brukere over hele verden.
Å omfavne disse valideringsstrategiene og beste praksis vil føre til mer motstandsdyktige applikasjoner som kan tåle kompleksiteten av ulike datakilder og brukerinteraksjoner på tvers av forskjellige regioner og kulturer. Invester i grundig validering; det er en investering i kvaliteten og påliteligheten til programvaren din.