22
33Runtime types for (de)serializing HTTP requests from both the client and server side
44
5+ ## Contents
6+
7+ - [ @api-ts/io-ts-http ] ( #api-tsio-ts-http )
8+ - [ Contents] ( #contents )
9+ - [ Preface] ( #preface )
10+ - [ Introduction] ( #introduction )
11+ - [ Overview] ( #overview )
12+ - [ Example] ( #example )
13+ - [ ` apiSpec ` ] ( #apispec )
14+ - [ ` httpRoute ` ] ( #httproute )
15+ - [ ` path ` ] ( #path )
16+ - [ ` method ` ] ( #method )
17+ - [ ` request ` ] ( #request )
18+ - [ ` response ` ] ( #response )
19+ - [ ` httpRequest ` ] ( #httprequest )
20+ - [ ` params ` ] ( #params )
21+ - [ ` query ` ] ( #query )
22+ - [ ` headers ` ] ( #headers )
23+ - [ ` body ` ] ( #body )
24+ - [ Decoding an ` httpRequest ` ] ( #decoding-an-httprequest )
25+ - [ Documentation] ( #documentation )
26+
27+ ## Preface
28+
529This package extends [ io-ts] ( https://github.com/gcanti/io-ts ) with functionality useful
6- for typing http requests. Start there for base knowledge required to use this package.
30+ for typing HTTP requests. Start there for base knowledge required to use this package.
31+
32+ ## Introduction
33+
34+ io-ts-http is the definition language for api-ts specifications, which define the API
35+ contract for a web sever to an arbitrary degree of precision. Web servers can use the
36+ io-ts-http spec to parse HTTP requests at runtime, and encode HTTP responses. Clients
37+ can use the io-ts-http spec to enforce API compatibility at compile time, and to encode
38+ requests to the server and decode responses.
739
840## Overview
941
10- The primary function in this library is ` httpRequest ` , which is used to build codecs
42+ The primary function in this library is ` httpRequest ` . You can use this to build codecs
1143which can parse a generic HTTP request into a more refined type. The generic HTTP
1244request should conform to the following interface:
1345
@@ -19,6 +51,9 @@ interface GenericHttpRequest {
1951 query: {
2052 [K : string ]: string | string [];
2153 };
54+ headers: {
55+ [K : string ]: string ;
56+ };
2257 body? : unknown ;
2358}
2459```
@@ -81,6 +116,264 @@ possible to encode the API contract (or at least as much of it that is possible
81116express in the type system) and therefore someone calling the API can be confident that
82117the server will correctly interpret a request if the arguments typecheck.
83118
119+ ## Example
120+
121+ Let's define the api-ts spec for a hypothetical ` message-user ` service. The conventional
122+ top-level export is an
123+ [ ` apiSpec ` ] ( https://github.com/BitGo/api-ts/blob/master/packages/io-ts-http/docs/apiSpec.md )
124+ value; for example:
125+
126+ ### ` apiSpec `
127+
128+ ``` typescript
129+ import { apiSpec } from ' @api-ts/io-ts-http' ;
130+
131+ import { GetMessage , CreateMessage } from ' ./routes/message' ;
132+ import { GetUser , CreateUser , UpdateUser , DeleteUser } from ' ./routes/user' ;
133+
134+ /**
135+ * message-user service
136+ *
137+ * @version 1.0.0
138+ */
139+ export const API = apiSpec ({
140+ ' api.v1.message' : {
141+ get: GetMessage ,
142+ post: CreateMessage ,
143+ },
144+ ' api.v1.user' : {
145+ get: GetUser ,
146+ post: CreateUser ,
147+ put: UpdateUser ,
148+ delete: DeleteUser ,
149+ },
150+ });
151+ ```
152+
153+ The ` apiSpec ` is imported, along with some named ` httpRoute ` s (` {Get|Create}Message ` ,
154+ and ` {Get|Create|Update|Delete}User ` ) [ which we'll discuss below] ( #httproute ) .
155+
156+ > Currently, if you add the ` @version ` JSDoc tag to the exported API spec, it will be
157+ > used as the API ` version ` when generating an OpenAPI schema. Support for other tags
158+ > may be added in the future.
159+
160+ The top-level export for ` message-user-types ` is ` API ` , which we define as an ` apiSpec `
161+ with two endpoints ` api/v1/message ` and ` api/v1/user ` . The ` api/v1/message ` endpoint
162+ responds to ` GET ` and ` POST ` verbs while the second reponds to ` GET ` , ` POST ` , ` PUT ` , and
163+ ` DELETE ` verbs using ` httpRoute ` s defined in ` ./routes/message ` . The following are the
164+ ` httpRoute ` s defined in ` ./routes/message ` .
165+
166+ ### ` httpRoute `
167+
168+ ``` typescript
169+ import * as t from ' io-ts' ;
170+ import { httpRoute , httpRequest } from ' @api-ts/io-ts-http' ;
171+
172+ export const GetMessage = httpRoute ({
173+ path: ' /message/{id}' ,
174+ method: ' GET' ,
175+ request: httpRequest ({
176+ params: {
177+ id: t .string ,
178+ },
179+ }),
180+ response: {
181+ 200 : t .type ({
182+ id: t .string ,
183+ message: t .string ,
184+ }),
185+ 404 : t .type ({
186+ error: t .string ,
187+ }),
188+ },
189+ });
190+
191+ export const CreateMessage = httpRoute ({
192+ path: ' /message' ,
193+ method: ' POST' ,
194+ request: httpRequest ({
195+ body: {
196+ message: t .string ,
197+ },
198+ }),
199+ response: {
200+ 200 : t .type ({
201+ id: t .string ,
202+ message: t .string ,
203+ }),
204+ 404 : t .type ({
205+ error: t .string ,
206+ }),
207+ },
208+ });
209+ ```
210+
211+ The first import is the ` io-ts ` package. It's usually imported ` as t ` for use in
212+ describing the types of data properties. Again, review
213+ [ io-ts] ( https://github.com/gcanti/io-ts ) documentation for more context on how to use it
214+ and this package.
215+
216+ Then ` httpRoute ` and ` httpRequest ` are imported. We'll review the
217+ [ ` httpRequest ` ] ( #httprequest ) below, but first, let's review the ` GetMessage `
218+ ` httpRoute ` .
219+
220+ ``` typescript
221+ export const GetMessage = httpRoute ({
222+ path: ' /message/{id}' ,
223+ method: ' GET' ,
224+ request: httpRequest ({
225+ params: {
226+ id: t .string ,
227+ },
228+ }),
229+ response: {
230+ 200 : t .type ({
231+ id: t .string ,
232+ message: t .string ,
233+ }),
234+ 404 : t .type ({
235+ error: t .string ,
236+ }),
237+ },
238+ });
239+ ```
240+
241+ [ ` httpRoute ` ] ( https://github.com/BitGo/api-ts/blob/master/packages/io-ts-http/docs/httpRoute.md ) s
242+ ` apiSpec ` s use
243+ [ ` httpRoute ` ] ( https://github.com/BitGo/api-ts/blob/master/packages/io-ts-http/docs/httpRoute.md ) s
244+ to define the ` path ` , ` method ` , ` request ` and ` response ` of a route.
245+
246+ #### ` path `
247+
248+ The route's ` path ` along with possible path variables. You should surround variables
249+ with brackets like ` {name} ` , and are to the ` request ` codec under the ` params ` property.
250+
251+ #### ` method `
252+
253+ The route's ` method ` is the
254+ [ HTTP request method] ( https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods ) to use
255+ for that route. In our ` GetMessage ` example, the ` method ` is ` GET ` , while in our
256+ ` PostMessage ` example, the ` method ` is ` POST ` .
257+
258+ #### ` request `
259+
260+ The route's ` request ` is the output of the ` httpRequest ` function. This will be
261+ described below.
262+
263+ #### ` response `
264+
265+ The route's ` response ` describes the possible
266+ [ HTTP responses] ( https://developer.mozilla.org/en-US/docs/Web/HTTP/Status ) the route can
267+ produce. The key-value pairs of the ` response ` object are an HTTP status code followed
268+ by the ` io-ts ` type of the response body. In our ` GetMessage ` example, a ` 200 ` status
269+ response yields a payload of a JSON object with two properties, ` message ` which is a
270+ ` string ` and ` id ` which is also a ` string ` , and a ` 404 ` yeilds a payload of a JSON
271+ object with a single property ` error ` which is a ` String ` .
272+
273+ ### ` httpRequest `
274+
275+ Use ` httpRequest ` to build the expected type that you pass in a request to the route. In
276+ our example ` GetMessage `
277+
278+ ``` typescript
279+ export const GetMessage = httpRoute ({
280+ path: ' /message/{id}' ,
281+ method: ' GET' ,
282+ request: httpRequest ({
283+ params: {
284+ id: t .string ,
285+ },
286+ }),
287+ // ...
288+ });
289+ ```
290+
291+ ` httpRequest ` s have a total of 4 optional properties: ` params ` (shown in the example),
292+ ` query ` , ` headers ` , and ` body ` .
293+
294+ #### ` params `
295+
296+ ` params ` is an object representing the types of path parameters in a URL. Any URL
297+ parameters in the ` path ` property of an ` httpRoute ` must be accounted for in the
298+ ` params ` property of the ` httpRequest ` . Our request has a single URL parameter it is
299+ expecting, ` id ` . This is the type of this parameter is captured in the ` params ` object
300+ of our ` httpRequest ` .
301+
302+ #### ` query `
303+
304+ ` query ` is the object representing the values passed in via query parameters at the end
305+ of a URL. The following example uses a new route, ` GetMessages ` , to our API that
306+ searches messages related to a specific ` author ` :
307+
308+ ``` typescript
309+ export const GetMessages = httpRoute ({
310+ path: ' /messages' ,
311+ method: ' GET' ,
312+ request: httpRequest ({
313+ query: {
314+ author: t .string ,
315+ },
316+ }),
317+ // ...
318+ });
319+ ```
320+
321+ #### ` headers `
322+
323+ ` headers ` is an object representing the types of the
324+ [ HTTP headers] ( https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers ) passed in with
325+ a request.
326+
327+ #### ` body `
328+
329+ ` body ` is an object representing the type of the
330+ [ HTTP body] ( https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#body ) of a
331+ request. Often this is a JSON object. The ` CreateMessage ` ` httpRoute ` in our example
332+ uses the ` body ` property:
333+
334+ ``` typescript
335+ export const CreateMessage = httpRoute ({
336+ path: ' /message' ,
337+ method: ' POST' ,
338+ request: httpRequest ({
339+ body: {
340+ message: t .string ,
341+ },
342+ }),
343+ // ...
344+ });
345+ ```
346+
347+ #### Decoding an ` httpRequest `
348+
349+ When you decode ` httpRequests ` using ` io-ts ` helpers, the properties of the request are
350+ flattened like this:
351+
352+ ``` typescript
353+ import { DateFromString , NumberFromString } from ' io-ts-types' ;
354+
355+ // build an httpRequest with one parameter id and a body with content and a timestamp
356+ const Request = httpRequest ({
357+ params: {
358+ id: NumberFromString ,
359+ },
360+ body: {
361+ content: t .string ,
362+ timestamp: DateFromISOString ,
363+ },
364+ });
365+
366+ // use io-ts to get the type of the Request
367+ type Request = t .TypeOf <typeof Request >;
368+
369+ // same as
370+ type Request = {
371+ id: number ;
372+ content: string ;
373+ timestamp: Date ;
374+ };
375+ ```
376+
84377## Documentation
85378
86379- [ API Reference] ( https://github.com/BitGo/api-ts/blob/master/packages/io-ts-http/docs/apiReference.md )
0 commit comments