@vladbasin/strong-api-mapping
Version:
Strongly typed API models. Mapping & validation
157 lines (115 loc) • 4.97 kB
Markdown
# Strong API mapping
Strongly typed API models. Mapping & validation. Use meaningful decorators. Don't repeat yourself.
## Installation
### npm
`npm install reflect-metadata /strong-api-mapping`
### yarn
`yarn add reflect-metadata /strong-api-mapping`
## Usage
This is a generic mapping library. You can integrate it with any HTTP stack. Currently the following packages use this library:
- [/strong-api-middleware](https://github.com/vladbasin/strong-api-middleware) - generic middleware library which can integrate with any HTTP stack, by introducing additional layers of request/response pipeline and with help of this library can support any HTTP stack.
- [/strong-api-middleware-aws-lambda](https://github.com/vladbasin/strong-api-middleware) - complete e2e HTTP pipeline integration for AWS Lambda
- [/strong-api-middleware-express]() - IN PROGRESS - complete e2e HTTP pipeline integration for express server
- [/strong-api-client](https://github.com/vladbasin/strong-api-client) - HTTP client which can use shared models to execute request. Therefore, no need to duplicate mapping & validation logic for API consumers & producers.
### Step-by-step guide
1. Import `reflect-metadata` ONCE in your index file:
```typescript
import 'reflect-metadata';
```
2. Define your model
```typescript
import { body, header, path, query } from '@vladbasin/strong-api-mapping';
export class RequestPayload {
()
public userId!: number;
({ key: 'userId' })
public id!: number;
()
public name!: string;
()
public isAdmin!: boolean;
({ key: 'lastname' })
public surname!: string;
({ parser: String })
public cars!: string[];
({ parser: Number })
public cash!: number[];
()
public details!: DetailsType;
({ key: 'Content-Type' })
public contentType!: string;
({ key: 'X-Info', parser: String })
public info!: string[];
}
```
3. Define validation rules with `Joi`
```typescript
export const RequestPayloadSchema = Joi.object<RequestPayload>({
surname: Joi.string().min(10),
cars: Joi.array().max(3),
// other rules for field content...
});
```
4. Prepare `RawApiRequest` for mapping.
For example, [/strong-api-middleware-aws-lambda](https://github.com/vladbasin/strong-api-middleware) already does it for you for AWS Lambda. But you can create your own middleware for your stack and use this library to do mapping & validation for you.
```typescript
// represents Query, Header, Route and Body values for HTTP request
export type RawApiRequestType = {
queryParams?: MaybeNullable<Record<string, Maybe<string>>>;
multiValueQueryParams?: MaybeNullable<Record<string, Maybe<string[]>>>;
pathParams?: MaybeNullable<Record<string, Maybe<string>>>;
headers?: MaybeNullable<Record<string, Maybe<string>>>;
multiValueHeaders?: MaybeNullable<Record<string, Maybe<string[]>>>;
body?: MaybeNullable<string>;
};
```
5. Call the following methods to map HTTP request to your model and vice versa with DRY principle
```typescript
// maps RawApiRequest to Model (RequestPayload) and validates it (throws `CodedError` with information if model is not valid)
mapRawApiRequestToPayload<RequestPayload>({
rawApiRequest,
PayloadConstructor: RequestPayload,
schema: RequestPayloadSchema,
});
// maps Model (RequestPayload) to RawApiRequest
mapPayloadToRawApiRequest(requestPayload);
```
Also applicable to **response** models (`mapRawApiResponseToPayload()`, `mapPayloadToRawApiResponse()`).
In case validation fails, the library throws `CodedError` instance with information which properties are not valid:
```typescript
{
message: 'ValidationError: "surname" is required'
code: 'ValidationFailed',
errors: [ { code: 'surname', message: 'any.required' } ],
}
```
Thus, you can share request/response models with your API consumers, so they don't need to repeat the same mapping & validation logic. See: [@vladbasin/strong-api-client](https://github.com/vladbasin/strong-api-client)
## Custom decorator/mapping
You can also specify custom mappings for custom decorator:
1. Define custom decorator
```typescript
import { defineDecorator, ParserType } from '@vladbasin/strong-api-mapping';
export const context = (options: { key?: string; parser?: ParserType }): PropertyDecorator =>
defineDecorator({
source: 'context',
useKey: true,
isKeyCaseSensitive: false,
key: options.key,
parser: options.parser,
isCustom: true,
});
```
2. Specify data for custom context
```typescript
mapRawApiRequestToPayload<RequestPayload>({
rawApiRequest,
PayloadConstructor: RequestPayload,
schema: RequestPayloadSchema,
customApiRequestData: {
context: {
customKey: 'customValue',
//...
},
},
});
```