Skip to content

Commit cdae61a

Browse files
authored
Merge pull request BitGo#24 from bitgopatmcl/BG-45706-superagent-how-to
feat: add how-to guide for superagent-codec-adapter
2 parents 7374276 + 41900c5 commit cdae61a

File tree

1 file changed

+147
-2
lines changed

1 file changed

+147
-2
lines changed
Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,148 @@
1-
# Superagent Codec Adaptor
1+
# Superagent Codec Adapter
22

3-
### TODO: BG-37914 add usage examples
3+
Combines an api spec with `superagent`/`supertest` to create a type-checked api client.
4+
5+
## Getting started
6+
7+
First, either define or import an `io-ts-http` api spec. The following one will be used
8+
for this guide:
9+
10+
```typescript
11+
import * as h from '@bitgo/io-ts-http';
12+
import * as t from 'io-ts';
13+
import { NumberFromString } from 'io-ts-types';
14+
15+
export const Example = t.type({
16+
foo: t.string,
17+
bar: t.number,
18+
});
19+
20+
export const GenericAPIError = t.type({
21+
message: t.string,
22+
});
23+
24+
export const PutExample = h.httpRoute({
25+
path: '/example/{id}',
26+
method: 'PUT',
27+
request: h.httpRequest({
28+
params: {
29+
id: NumberFromString,
30+
},
31+
body: {
32+
example: Example,
33+
},
34+
}),
35+
response: {
36+
ok: Example,
37+
invalidRequest: GenericAPIError,
38+
},
39+
});
40+
41+
export const ExampleAPI = h.apiSpec({
42+
'api.example': {
43+
put: PutExample,
44+
},
45+
});
46+
```
47+
48+
`ExampleAPI` can then be used to create a type-safe api client for either `superagent`
49+
or `supertest`. This requires two steps: wrapping the `superagent`/`supertest` instance,
50+
then binding it to the api spec.
51+
52+
For `superagent`:
53+
54+
```typescript
55+
import {
56+
superagentRequestFactory,
57+
buildApiClient,
58+
} from '@bitgo/superagent-codec-adapter';
59+
import superagent from 'superagent';
60+
61+
import { ExampleAPI } from './see-the-above-example';
62+
63+
// The api root, in a real project probably coming from a config option
64+
const BASE_URL = 'http://example.com/';
65+
66+
// Step one: wrap `superagent` and the api root
67+
const requestFactory = superagentRequestFactory(superagent, BASE_URL);
68+
69+
// Step two: combine the request factory and imported api spec into an api client
70+
// This is intended to be exported and used
71+
export const apiClient = buildApiClient(requestFactory, ExampleAPI);
72+
```
73+
74+
For `supertest` the process is almost identical except that `supertest` itself handles
75+
knowing the root api url:
76+
77+
```typescript
78+
import {
79+
supertestRequestFactory,
80+
buildApiClient,
81+
} from '@bitgo/superagent-codec-adapter';
82+
import supertest from 'superagent';
83+
84+
import { ExampleAPI } from './see-the-above-example';
85+
86+
// For the purposes of this guide, say we have an Express app that can be imported from the project.
87+
// See the `supertest` docs for the options it has for instantiation.
88+
import { app } from '../src/index';
89+
const request = supertest(app);
90+
91+
// Step one: wrap the `supertest` request function created above
92+
const requestFactory = superatestRequestFactory(request);
93+
94+
// Step two: combine the request factory and imported api spec into an api client
95+
// This is intended to be exported and used
96+
export const apiClient = buildApiClient(requestFactory, ExampleAPI);
97+
```
98+
99+
The resulting `apiClient` can then be imported elsewhere and used:
100+
101+
```typescript
102+
import { apiClient } from './api-client-example';
103+
104+
const doSomething = async () => {
105+
// The `api.example` here comes from the operation in the `ExampleAPI` definition from above
106+
const response = await apiClient['api.example']
107+
// The object passed to this function is type-checked against the request codec
108+
.put({ id: 42, example: { foo: 'hello', bar: 1 } })
109+
// This will use the set of response codecs to decode the response
110+
.decode();
111+
112+
// The two main properties on `response` are `status` and `body`
113+
// If the value of `status` is checked, then TypeScript will infer the correct body type
114+
if (response.status === 200) {
115+
const { foo, bar } = response.body; // We know the correct body type here
116+
} else if (response.status === 400) {
117+
// The body is a GenericAPIError
118+
console.log(response.body.message);
119+
} else {
120+
// In case an unexpected status code comes back, or the response body does not correctly
121+
// decode, we can still access it as an `unknown` type.
122+
if (
123+
response.body &&
124+
typeof response.body === 'object' &&
125+
response.body.hasOwnProperty('message')
126+
) {
127+
console.log(response.body.message);
128+
}
129+
}
130+
};
131+
```
132+
133+
For convenience, a `decodeExpecting` function is also added to requests. It accepts an
134+
HTTP status code and throws if either the response code doesn't match, or it does but
135+
the response body failed to decode.
136+
137+
```typescript
138+
const expectOk = async () => {
139+
// The `api.example` here comes from the operation in the `ExampleAPI` definition from above
140+
const response = await apiClient['api.example']
141+
// The object passed to this function is type-checked against the request codec
142+
.put({ id: 42, example: { foo: 'hello', bar: 1 } })
143+
// This will use the set of response codecs to decode the response
144+
.decodeExpecting(200);
145+
146+
const { foo, bar } = response.body;
147+
};
148+
```

0 commit comments

Comments
 (0)