You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
> ⚠️ **rescript-rest** relies on **rescript-schema** which uses `eval` for parsing. It's usually fine but might not work in some environments like Cloudflare Workers or third-party scripts used on pages with the [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) header.
20
20
21
-
## Tutorials
22
-
23
-
- Building and consuming REST API in ReScript with rescript-rest and Fastify ([YouTube](https://youtu.be/37FY6a-zY20?si=72zT8Gecs5vmDPlD))
24
-
25
21
## Super Simple Example
26
22
27
-
Easily define your API contract somewhere shared, for example, `Contract.res`:
23
+
Define your API contract somewhere shared, for example, `Contract.res`:
28
24
29
25
```rescript
30
26
let getPosts = Rest.route(() => {
@@ -44,11 +40,17 @@ let getPosts = Rest.route(() => {
44
40
})
45
41
```
46
42
43
+
Set an endpoint your fetch calls should use:
44
+
45
+
```rescript
46
+
// Contract.res
47
+
Rest.setGlobalClient("http://localhost:3000")
48
+
```
49
+
47
50
Consume the API on the client with a RPC-like interface:
48
51
49
52
```rescript
50
53
let result = await Contract.getPosts->Rest.fetch(
51
-
"http://localhost:3000",
52
54
{"skip": 0, "take": 10, "page": Some(1)}
53
55
// ^-- Fully typed!
54
56
) // ℹ️ It'll do a GET request to http://localhost:3000/posts?skip=0&take=10 with the `{"x-pagination-page": "1"}` headers
@@ -85,11 +87,41 @@ let _ = app->Fastify.listen({port: 3000})
@@ -104,7 +136,13 @@ Add `rescript-rest` to `bs-dependencies` in your `rescript.json`:
104
136
}
105
137
```
106
138
107
-
## Path Parameters
139
+
## Route Definition
140
+
141
+
Routes are the main building block of the library and a perfect way to describe a contract between your client and server.
142
+
143
+
For every route you can describe how the HTTP transport will look like, the `'input` and `'output` types, as well as add additional metadata to use for OpenAPI.
144
+
145
+
### Path Parameters
108
146
109
147
You can define path parameters by adding them to the `path` strin with a curly brace `{}` including the parameter name. Then each parameter must be defined in `input` with the `s.param` method.
110
148
@@ -121,8 +159,7 @@ let getPost = Rest.route(() => {
@@ -132,7 +169,7 @@ let result = await client.call(
132
169
133
170
If you would like to run validations or transformations on the path parameters, you can use [`rescript-schema`](https://github.com/DZakh/rescript-schema) features for this. Note that the parameter names in the `s.param`**must** match the parameter names in the `path` string.
134
171
135
-
## Query Parameters
172
+
###Query Parameters
136
173
137
174
You can add query parameters to the request by using the `s.query` method in the `input` definition.
138
175
@@ -149,8 +186,7 @@ let getPosts = Rest.route(() => {
149
186
],
150
187
})
151
188
152
-
let result = await client.call(
153
-
getPosts,
189
+
let result = await getPosts->Rest.fetch(
154
190
{
155
191
"skip": 0,
156
192
"take": 10,
@@ -160,11 +196,11 @@ let result = await client.call(
160
196
161
197
You can also configure rescript-rest to encode/decode query parameters as JSON by using the `jsonQuery` option. This allows you to skip having to do type coercions, and allow you to use complex and typed JSON objects.
162
198
163
-
## Request Headers
199
+
###Request Headers
164
200
165
201
You can add headers to the request by using the `s.header` method in the `input` definition.
166
202
167
-
### Authentication header
203
+
####Authentication Header
168
204
169
205
For the Authentication header there's an additional helper `s.auth` which supports `Bearer` and `Basic` authentication schemes.
170
206
@@ -181,16 +217,15 @@ let getPosts = Rest.route(() => {
181
217
],
182
218
})
183
219
184
-
let result = await client.call(
185
-
getPosts,
220
+
let result = await getPosts->Rest.fetch(
186
221
{
187
222
"token": "abc",
188
223
"pagination": 10,
189
224
}
190
225
) // ℹ️ It'll do a GET request to http://localhost:3000/posts with the `{"authorization": "Bearer abc", "x-pagination": "10"}` headers
191
226
```
192
227
193
-
## Raw Body
228
+
###Raw Body
194
229
195
230
For some low-level APIs, you may need to send raw body without any additional processing. You can use `s.rawBody` method to define a raw body schema. The schema should be string-based, but you can apply transformations to it using `s.variant` or `s.transform` methods.
196
231
@@ -217,10 +252,8 @@ let getLogs = Rest.route(() => {
217
252
],
218
253
})
219
254
220
-
let result = await client.call(
221
-
getLogs,
222
-
"debug"
223
-
) // ℹ️ It'll do a POST request to http://localhost:3000/logs with the body `{"size": 20, "query": {"bool": {"must": [{"terms": {"log.level": ["debug"]}}]}}}` and the headers `{"content-type": "application/json"}`
255
+
let result = await getLogs->Rest.fetch("debug")
256
+
// ℹ️ It'll do a POST request to http://localhost:3000/logs with the body `{"size": 20, "query": {"bool": {"must": [{"terms": {"log.level": ["debug"]}}]}}}` and the headers `{"content-type": "application/json"}`
224
257
```
225
258
226
259
You can also use routes with `rawBody` on the server side with Fastify as any other route:
> 🧠 Currently Raw Body is sent with the application/json Content Type. If you need support for other Content Types, please open an issue or PR.
235
268
236
-
## Responses
269
+
###Responses
237
270
238
271
Responses are described as an array of response definitions. It's possible to assign the definition to a specific status using `s.status` method.
239
272
@@ -283,7 +316,7 @@ let createPost = Rest.route(() => {
283
316
```
284
317
-->
285
318
286
-
## Response Headers
319
+
###Response Headers
287
320
288
321
Responses from an API can include custom headers to provide additional information on the result of an API call. For example, a rate-limited API may provide the rate limit status via response headers as follows:
289
322
@@ -317,7 +350,7 @@ let ping = Rest.route(() => {
317
350
})
318
351
```
319
352
320
-
## Temporary Redirect
353
+
###Temporary Redirect
321
354
322
355
You can define a redirect using Route response definition:
323
356
@@ -347,6 +380,57 @@ let route = Rest.route(() => {
347
380
348
381
In a nutshell, the `redirect` function is a wrapper around `s.status(307)` and `s.header("location", schema)`.
349
382
383
+
## Fetch & Client
384
+
385
+
To call `Rest.fetch` you either need to explicitely pass a `client` as an argument or have it globally set.
386
+
387
+
I recommend to set a global client in the contract file:
388
+
389
+
```rescript
390
+
// Contract.res
391
+
Rest.setGlobalClient("http://localhost:3000")
392
+
```
393
+
394
+
If you pass the endpoint via environment variables, I recommend using my another library [rescript-envsafe](https://github.com/DZakh/rescript-envsafe):
If you can't or don't want to use a global client, you can manually pass it to the `Rest.fetch`:
415
+
416
+
```rescript
417
+
let client = Rest.client(PublicEnv.apiEndpoint)
418
+
419
+
await route->Rest.fetch(input, ~client)
420
+
```
421
+
422
+
This might be useful when you interact with multiple backends in a single application. For this case I recommend to have a separate contract file for every backend and include wrappers for fetch with already configured client:
423
+
424
+
```rescript
425
+
let client = Rest.client(PublicEnv.apiEndpoint)
426
+
427
+
let fetch = Rest.fetch(~client, ...)
428
+
```
429
+
430
+
### API Fetcher
431
+
432
+
You can override the client fetching logic by passing the `~apiFetcher` param.
0 commit comments