@effect/platform 0.72.0 → 0.72.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +769 -260
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -9,26 +9,185 @@ Welcome to the documentation for `@effect/platform`, a library designed for crea
9
9
 
10
10
  ## Overview
11
11
 
12
- The `HttpApi` family of modules provide a declarative way to define HTTP APIs.
13
- You can create an API by combining multiple endpoints, each with its own set of
14
- schemas that define the request and response types.
12
+ The `HttpApi` modules offer a flexible and declarative way to define HTTP APIs. You build an API by combining endpoints, each describing its path and the request/response schemas. Once defined, the same API definition can be used to:
15
13
 
16
- After you have defined your API, you can use it to implement a server or derive
17
- a client that can interact with the server.
14
+ - Spin up a server
15
+ - Provide a Swagger documentation page
16
+ - Derive a fully-typed client
18
17
 
19
- ## Defining an API
18
+ This separation helps avoid duplication, keeps everything up to date, and simplifies maintenance when your API evolves. It also makes it straightforward to add new functionality or reconfigure existing endpoints without changing the entire stack.
20
19
 
21
- To define an API, you need to create a set of endpoints. Each endpoint is
22
- defined by a path, a method, and a set of schemas that define the request and
23
- response types.
20
+ ## Hello World
24
21
 
25
- Each set of endpoints is added to an `HttpApiGroup`, which can be combined with
26
- other groups to create a complete API.
22
+ Here is a simple example of defining an API with a single endpoint that returns a string:
27
23
 
28
- ### Your first `HttpApiGroup`
24
+ **Example** (Defining an API)
29
25
 
30
- Let's define a simple CRUD API for managing users. First, we need to make an
31
- `HttpApiGroup` that contains our endpoints.
26
+ ```ts
27
+ import {
28
+ HttpApi,
29
+ HttpApiBuilder,
30
+ HttpApiEndpoint,
31
+ HttpApiGroup
32
+ } from "@effect/platform"
33
+ import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
34
+ import { Effect, Layer, Schema } from "effect"
35
+ import { createServer } from "node:http"
36
+
37
+ // Define our API with one group named "Greetings" and one endpoint called "hello-world"
38
+ const MyApi = HttpApi.make("MyApi").add(
39
+ HttpApiGroup.make("Greetings").add(
40
+ HttpApiEndpoint.get("hello-world")`/`.addSuccess(Schema.String)
41
+ )
42
+ )
43
+
44
+ // Implement the "Greetings" group
45
+ const GreetingsLive = HttpApiBuilder.group(MyApi, "Greetings", (handlers) =>
46
+ handlers.handle("hello-world", () => Effect.succeed("Hello, World!"))
47
+ )
48
+
49
+ // Provide the implementation for the API
50
+ const MyApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(GreetingsLive))
51
+
52
+ // Set up the server using NodeHttpServer on port 3000
53
+ const ServerLive = HttpApiBuilder.serve().pipe(
54
+ Layer.provide(MyApiLive),
55
+ Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
56
+ )
57
+
58
+ // Run the server
59
+ Layer.launch(ServerLive).pipe(NodeRuntime.runMain)
60
+ ```
61
+
62
+ Navigate to `http://localhost:3000` in your browser to see the response "Hello, World!".
63
+
64
+ ### Serving The Auto Generated Swagger Documentation
65
+
66
+ You can add Swagger documentation to your API by including the `HttpApiSwagger` module. Provide the `HttpApiSwagger.layer` in your server setup, as shown here:
67
+
68
+ **Example** (Serving Swagger Documentation)
69
+
70
+ ```ts
71
+ import {
72
+ HttpApi,
73
+ HttpApiBuilder,
74
+ HttpApiEndpoint,
75
+ HttpApiGroup,
76
+ HttpApiSwagger
77
+ } from "@effect/platform"
78
+ import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
79
+ import { Effect, Layer, Schema } from "effect"
80
+ import { createServer } from "node:http"
81
+
82
+ const MyApi = HttpApi.make("MyApi").add(
83
+ HttpApiGroup.make("Greetings").add(
84
+ HttpApiEndpoint.get("hello-world")`/`.addSuccess(Schema.String)
85
+ )
86
+ )
87
+
88
+ const GreetingsLive = HttpApiBuilder.group(MyApi, "Greetings", (handlers) =>
89
+ handlers.handle("hello-world", () => Effect.succeed("Hello, World!"))
90
+ )
91
+
92
+ const MyApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(GreetingsLive))
93
+
94
+ const ServerLive = HttpApiBuilder.serve().pipe(
95
+ // Provide the Swagger layer so clients can access auto-generated docs
96
+ Layer.provide(HttpApiSwagger.layer()),
97
+ Layer.provide(MyApiLive),
98
+ Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
99
+ )
100
+
101
+ Layer.launch(ServerLive).pipe(NodeRuntime.runMain)
102
+ ```
103
+
104
+ Navigate to `http://localhost:3000/docs` in your browser to see the Swagger documentation:
105
+
106
+ ![Swagger Documentation](./images/swagger-hello-world.png)
107
+
108
+ ### Deriving a Client
109
+
110
+ After you define your API, you can generate a client to interact with the server. The `HttpApiClient` module provides the needed tools:
111
+
112
+ **Example** (Deriving a Client)
113
+
114
+ ```ts
115
+ import {
116
+ FetchHttpClient,
117
+ HttpApi,
118
+ HttpApiBuilder,
119
+ HttpApiClient,
120
+ HttpApiEndpoint,
121
+ HttpApiGroup,
122
+ HttpApiSwagger
123
+ } from "@effect/platform"
124
+ import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
125
+ import { Effect, Layer, Schema } from "effect"
126
+ import { createServer } from "node:http"
127
+
128
+ const MyApi = HttpApi.make("MyApi").add(
129
+ HttpApiGroup.make("Greetings").add(
130
+ HttpApiEndpoint.get("hello-world")`/`.addSuccess(Schema.String)
131
+ )
132
+ )
133
+
134
+ const GreetingsLive = HttpApiBuilder.group(MyApi, "Greetings", (handlers) =>
135
+ handlers.handle("hello-world", () => Effect.succeed("Hello, World!"))
136
+ )
137
+
138
+ const MyApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(GreetingsLive))
139
+
140
+ const ServerLive = HttpApiBuilder.serve().pipe(
141
+ Layer.provide(HttpApiSwagger.layer()),
142
+ Layer.provide(MyApiLive),
143
+ Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
144
+ )
145
+
146
+ Layer.launch(ServerLive).pipe(NodeRuntime.runMain)
147
+
148
+ // Create a program that derives and uses the client
149
+ const program = Effect.gen(function* () {
150
+ // Derive the client
151
+ const client = yield* HttpApiClient.make(MyApi, {
152
+ baseUrl: "http://localhost:3000"
153
+ })
154
+ // Call the "hello-world" endpoint
155
+ const hello = yield* client.Greetings["hello-world"]()
156
+ console.log(hello)
157
+ })
158
+
159
+ // Provide a Fetch-based HTTP client and run the program
160
+ Effect.runFork(program.pipe(Effect.provide(FetchHttpClient.layer)))
161
+ // Output: Hello, World!
162
+ ```
163
+
164
+ ## Basic Usage
165
+
166
+ To define an API, create a set of endpoints. Each endpoint is described by a path, a method, and schemas for the request and response.
167
+
168
+ Collections of endpoints are grouped in an `HttpApiGroup`, and multiple groups can be merged into a complete API.
169
+
170
+ ```
171
+ API
172
+ ├── Group
173
+ │ ├── Endpoint
174
+ │ └── Endpoint
175
+ └── Group
176
+ ├── Endpoint
177
+ ├── Endpoint
178
+ └── Endpoint
179
+ ```
180
+
181
+ ### Defining a HttpApiGroup
182
+
183
+ Below is a simple CRUD API for user management. We have an `HttpApiGroup` with the following endpoints:
184
+
185
+ - `GET /users/:userId` - Find a user by id
186
+ - `POST /users` - Create a new user
187
+ - `DELETE /users/:userId` - Delete a user by id
188
+ - `PATCH /users/:userId` - Update a user by id
189
+
190
+ **Example** (Defining a Group)
32
191
 
33
192
  ```ts
34
193
  import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform"
@@ -46,28 +205,26 @@ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
46
205
 
47
206
  const usersApi = HttpApiGroup.make("users")
48
207
  .add(
49
- // each endpoint has a name and a path
50
- // You can use a template string to define path parameter schemas
208
+ // Each endpoint has a name and a path.
209
+ // You can use a template string to define path parameters...
51
210
  HttpApiEndpoint.get("findById")`/users/${UserIdParam}`
52
- // the endpoint can have a Schema for a successful response
211
+ // Add a Schema for a successful response.
53
212
  .addSuccess(User)
54
213
  )
55
214
  .add(
56
- // you can also pass the path as a string and use `.setPath` to define the
57
- // path parameter schema
215
+ // ..or you can pass the path as a string and use `.setPath` to define path parameters.
58
216
  HttpApiEndpoint.post("create", "/users")
59
217
  .addSuccess(User)
60
- // and here is a Schema for the request payload / body
61
- //
62
- // this is a POST request, so the payload is in the body
63
- // but for a GET request, the payload would be in the URL search params
218
+ // Define a Schema for the request body.
219
+ // Since this is a POST, data is in the body.
220
+ // For GET requests, data could be in the URL search parameters.
64
221
  .setPayload(
65
222
  Schema.Struct({
66
223
  name: Schema.String
67
224
  })
68
225
  )
69
226
  )
70
- // by default, the endpoint will respond with a 204 No Content
227
+ // By default, this endpoint responds with 204 No Content.
71
228
  .add(HttpApiEndpoint.del("delete")`/users/${UserIdParam}`)
72
229
  .add(
73
230
  HttpApiEndpoint.patch("update")`/users/${UserIdParam}`
@@ -80,84 +237,83 @@ const usersApi = HttpApiGroup.make("users")
80
237
  )
81
238
  ```
82
239
 
83
- You can also extend the `HttpApiGroup` with a class to gain an opaque type.
84
- We will use this API style in the following examples:
240
+ You can also extend `HttpApiGroup` with a class to create an opaque type:
241
+
242
+ **Example** (Defining a Group with an Opaque Type)
85
243
 
86
244
  ```ts
87
245
  class UsersApi extends HttpApiGroup.make("users").add(
88
246
  HttpApiEndpoint.get("findById")`/users/${UserIdParam}`
89
- // ... same as above
247
+ // ... etc
90
248
  ) {}
91
249
  ```
92
250
 
93
- ### Creating the top level `HttpApi`
251
+ ### Creating the Top-Level HttpApi
94
252
 
95
- Once you have defined your groups, you can combine them into a single `HttpApi`.
96
-
97
- ```ts
98
- import { HttpApi } from "@effect/platform"
99
-
100
- class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
101
- ```
253
+ After defining your groups, you can combine them into a single `HttpApi` to represent the full set of endpoints for your application.
102
254
 
103
- Or with the non-opaque style:
255
+ **Example** (Combining Groups into a Top-Level API)
104
256
 
105
257
  ```ts
106
- const api = HttpApi.make("myApi").add(usersApi)
107
- ```
108
-
109
- ### Adding OpenApi annotations
110
-
111
- You can add OpenApi annotations to your API by using the `OpenApi` module.
258
+ import {
259
+ HttpApi,
260
+ HttpApiEndpoint,
261
+ HttpApiGroup,
262
+ HttpApiSchema
263
+ } from "@effect/platform"
264
+ import { Schema } from "effect"
112
265
 
113
- Let's add a title to our `UsersApi` group:
266
+ class User extends Schema.Class<User>("User")({
267
+ id: Schema.Number,
268
+ name: Schema.String,
269
+ createdAt: Schema.DateTimeUtc
270
+ }) {}
114
271
 
115
- ```ts
116
- import { OpenApi } from "@effect/platform"
272
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
117
273
 
118
274
  class UsersApi extends HttpApiGroup.make("users")
275
+ .add(HttpApiEndpoint.get("findById")`/users/${UserIdParam}`.addSuccess(User))
119
276
  .add(
120
- HttpApiEndpoint.get("findById")`/users/${UserIdParam}`
121
- // ... same as above
277
+ HttpApiEndpoint.post("create", "/users")
278
+ .addSuccess(User)
279
+ .setPayload(
280
+ Schema.Struct({
281
+ name: Schema.String
282
+ })
283
+ )
122
284
  )
123
- // add an OpenApi title & description
124
- // You can set one attribute at a time
125
- .annotate(OpenApi.Title, "Users API")
126
- // or multiple at once
127
- .annotateContext(
128
- OpenApi.annotations({
129
- title: "Users API",
130
- description: "API for managing users"
131
- })
285
+ .add(HttpApiEndpoint.del("delete")`/users/${UserIdParam}`)
286
+ .add(
287
+ HttpApiEndpoint.patch("update")`/users/${UserIdParam}`
288
+ .addSuccess(User)
289
+ .setPayload(
290
+ Schema.Struct({
291
+ name: Schema.String
292
+ })
293
+ )
132
294
  ) {}
133
- ```
134
295
 
135
- Now when you generate OpenApi documentation, the title and description will be
136
- included.
137
-
138
- You can also add OpenApi annotations to the top-level `HttpApi`:
296
+ // Combine the groups into a top-level API with an opaque style
297
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
139
298
 
140
- ```ts
141
- class MyApi extends HttpApi.make("myApi")
142
- .add(UsersApi)
143
- .annotate(OpenApi.Title, "My API") {}
299
+ // Alternatively, use a non-opaque style
300
+ const api = HttpApi.make("myApi").add(UsersApi)
144
301
  ```
145
302
 
146
303
  ### Adding errors
147
304
 
148
- You can add error responses to your endpoints using the following apis:
305
+ Error responses can be added to your endpoints to handle various scenarios. These responses can be specific to an endpoint, a group of endpoints, or the entire API.
149
306
 
150
- - `HttpApiEndpoint.addError` - add an error response for a single endpoint
151
- - `HttpApiGroup.addError` - add an error response for all endpoints in a group
152
- - `HttpApi.addError` - add an error response for all endpoints in the api
307
+ - Use `HttpApiEndpoint.addError` to add an error response to a specific endpoint.
308
+ - Use `HttpApiGroup.addError` to add an error response to all endpoints in a group.
309
+ - Use `HttpApi.addError` to add an error response to all endpoints in the API.
153
310
 
154
- The group & api level errors are useful for adding common error responses that
155
- can be used in middleware.
311
+ Group-level and API-level errors are particularly useful for handling common error scenarios, such as authentication failures, that might be managed through middleware.
156
312
 
157
- Here is an example of adding a 404 error to the `UsersApi` group:
313
+ **Example** (Adding Errors to Endpoints and Groups)
158
314
 
159
315
  ```ts
160
- // define the error schemas
316
+ // Define error schemas
161
317
  class UserNotFound extends Schema.TaggedError<UserNotFound>()(
162
318
  "UserNotFound",
163
319
  {}
@@ -171,33 +327,39 @@ class Unauthorized extends Schema.TaggedError<Unauthorized>()(
171
327
  class UsersApi extends HttpApiGroup.make("users")
172
328
  .add(
173
329
  HttpApiEndpoint.get("findById")`/users/${UserIdParam}`
174
- // here we are adding our error response
175
- .addError(UserNotFound, { status: 404 })
176
330
  .addSuccess(User)
331
+ // Add a 404 error response for this endpoint
332
+ .addError(UserNotFound, { status: 404 })
177
333
  )
178
- // or we could add an error to the group
179
- .addError(Unauthorized, { status: 401 }) {}
334
+ // Add a 401 error response to the entire group
335
+ .addError(Unauthorized, { status: 401 }) {
336
+ // ...etc
337
+ }
180
338
  ```
181
339
 
182
- It is worth noting that you can add multiple error responses to an endpoint,
183
- just by calling `HttpApiEndpoint.addError` multiple times.
340
+ You can add multiple error responses to a single endpoint by calling `HttpApiEndpoint.addError` multiple times. This allows you to handle different types of errors with specific status codes and descriptions, ensuring that the API behaves as expected in various scenarios.
184
341
 
185
- ### Multipart requests
342
+ ### Multipart Requests
186
343
 
187
- If you need to handle file uploads, you can use the `HttpApiSchema.Multipart`
188
- api to flag a `HttpApiEndpoint` payload schema as a multipart request.
344
+ To handle file uploads, you can use the `HttpApiSchema.Multipart` API to designate an endpoint's payload schema as a multipart request. This allows you to specify the structure of the expected multipart data, including file uploads, using the `Multipart` module.
189
345
 
190
- You can then use the schemas from the `Multipart` module to define the expected
191
- shape of the multipart request.
346
+ **Example** (Handling File Uploads)
192
347
 
193
348
  ```ts
194
- import { HttpApiSchema, Multipart } from "@effect/platform"
349
+ import {
350
+ HttpApiEndpoint,
351
+ HttpApiGroup,
352
+ HttpApiSchema,
353
+ Multipart
354
+ } from "@effect/platform"
355
+ import { Schema } from "effect"
195
356
 
196
357
  class UsersApi extends HttpApiGroup.make("users").add(
197
358
  HttpApiEndpoint.post("upload")`/users/upload`.setPayload(
359
+ // Mark the payload as a multipart request
198
360
  HttpApiSchema.Multipart(
199
361
  Schema.Struct({
200
- // add a "files" field to the schema
362
+ // Define a "files" field for the uploaded files
201
363
  files: Multipart.FilesSchema
202
364
  })
203
365
  )
@@ -205,27 +367,34 @@ class UsersApi extends HttpApiGroup.make("users").add(
205
367
  ) {}
206
368
  ```
207
369
 
370
+ This setup makes it clear that the endpoint expects a multipart request with a `files` field. The `Multipart.FilesSchema` automatically handles file data, making it easier to work with uploads in your application.
371
+
208
372
  ### Changing the response encoding
209
373
 
210
- By default, the response is encoded as JSON. You can change the encoding using
211
- the `HttpApiSchema.withEncoding` api.
374
+ By default, responses are encoded as JSON. If you need a different format, you can modify the encoding using the `HttpApiSchema.withEncoding` API. This allows you to specify both the type and content of the response.
212
375
 
213
- Here is an example of changing the encoding to text/csv:
376
+ **Example** (Changing Response Encoding to `text/csv`)
214
377
 
215
378
  ```ts
379
+ import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform"
380
+ import { Schema } from "effect"
381
+
382
+ // Define the UsersApi group with an endpoint that returns CSV data
216
383
  class UsersApi extends HttpApiGroup.make("users").add(
217
- HttpApiEndpoint.get("csv")`/users/csv`.addSuccess(
218
- Schema.String.pipe(
219
- HttpApiSchema.withEncoding({
220
- kind: "Text",
221
- contentType: "text/csv"
222
- })
384
+ HttpApiEndpoint.get("csv")`/users/csv`
385
+ // Define the success response as a string and set the encoding to CSV
386
+ .addSuccess(
387
+ Schema.String.pipe(
388
+ HttpApiSchema.withEncoding({
389
+ kind: "Text",
390
+ contentType: "text/csv"
391
+ })
392
+ )
223
393
  )
224
- )
225
394
  ) {}
226
395
  ```
227
396
 
228
- ## Implementing a server
397
+ ## Implementing a Server
229
398
 
230
399
  Now that you have defined your API, you can implement a server that serves the
231
400
  endpoints.
@@ -233,14 +402,47 @@ endpoints.
233
402
  The `HttpApiBuilder` module provides all the apis you need to implement your
234
403
  server.
235
404
 
236
- ### Implementing a `HttpApiGroup`
405
+ For semplicity we will use a `UsersApi` group with a single `findById` endpoint.
406
+
407
+ ```ts
408
+ import {
409
+ HttpApi,
410
+ HttpApiEndpoint,
411
+ HttpApiGroup,
412
+ HttpApiSchema
413
+ } from "@effect/platform"
414
+ import { Schema } from "effect"
415
+
416
+ class User extends Schema.Class<User>("User")({
417
+ id: Schema.Number,
418
+ name: Schema.String,
419
+ createdAt: Schema.DateTimeUtc
420
+ }) {}
421
+
422
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
423
+
424
+ class UsersApi extends HttpApiGroup.make("users").add(
425
+ HttpApiEndpoint.get("findById")`/users/${UserIdParam}`.addSuccess(User)
426
+ ) {}
427
+
428
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
429
+ ```
430
+
431
+ ### Implementing a HttpApiGroup
432
+
433
+ The `HttpApiBuilder.group` API is used to implement a specific group of endpoints within an `HttpApi` definition. It requires the following inputs:
434
+
435
+ | Input | Description |
436
+ | --------------------------------- | ----------------------------------------------------------------------- |
437
+ | The complete `HttpApi` definition | The overall API structure that includes the group you are implementing. |
438
+ | The name of the group | The specific group you are focusing on within the API. |
439
+ | A function to add handlers | A function that defines how each endpoint in the group is handled. |
237
440
 
238
- First up, let's implement an `UsersApi` group with a single `findById` endpoint.
441
+ Each endpoint in the group is connected to its logic using the `HttpApiBuilder.handle` method, which maps the endpoint's definition to its corresponding implementation.
239
442
 
240
- The `HttpApiBuilder.group` api takes the `HttpApi` definition, the group name,
241
- and a function that adds the handlers required for the group.
443
+ The `HttpApiBuilder.group` API produces a `Layer` that can later be provided to the server implementation.
242
444
 
243
- Each endpoint is implemented using the `HttpApiBuilder.handle` api.
445
+ **Example** (Implementing an API Group)
244
446
 
245
447
  ```ts
246
448
  import {
@@ -250,16 +452,14 @@ import {
250
452
  HttpApiGroup,
251
453
  HttpApiSchema
252
454
  } from "@effect/platform"
253
- import { DateTime, Effect, Layer, Schema } from "effect"
455
+ import { DateTime, Effect, Schema } from "effect"
254
456
 
255
- // here is our api definition
256
457
  class User extends Schema.Class<User>("User")({
257
458
  id: Schema.Number,
258
459
  name: Schema.String,
259
460
  createdAt: Schema.DateTimeUtc
260
461
  }) {}
261
462
 
262
- // Our user id path parameter schema
263
463
  const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
264
464
 
265
465
  class UsersApi extends HttpApiGroup.make("users").add(
@@ -272,29 +472,65 @@ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
272
472
  // Implementation
273
473
  // --------------------------------------------
274
474
 
275
- // the `HttpApiBuilder.group` api returns a `Layer`
276
- const UsersApiLive: Layer.Layer<HttpApiGroup.ApiGroup<"users">> =
475
+ // ┌─── Layer<HttpApiGroup.ApiGroup<"myApi", "users">>
476
+ // ▼
477
+ const UsersApiLive =
478
+ // ┌─── The Whole API
479
+ // │ ┌─── The Group you are implementing
480
+ // ▼ ▼
277
481
  HttpApiBuilder.group(MyApi, "users", (handlers) =>
278
- handlers
279
- // the parameters & payload are passed to the handler function.
280
- .handle("findById", ({ path: { userId } }) =>
482
+ handlers.handle(
483
+ // ┌─── The Endpoint you are implementing
484
+ // ▼
485
+ "findById",
486
+ // Provide the handler logic for the endpoint.
487
+ // The parameters & payload are passed to the handler function.
488
+ ({ path: { userId } }) =>
281
489
  Effect.succeed(
490
+ // Return a mock user object with the provided ID
282
491
  new User({
283
492
  id: userId,
284
493
  name: "John Doe",
285
494
  createdAt: DateTime.unsafeNow()
286
495
  })
287
496
  )
288
- )
497
+ )
289
498
  )
290
499
  ```
291
500
 
292
- ### Using services inside a `HttpApiGroup`
501
+ Using `HttpApiBuilder.group`, you connect the structure of your API to its logic, enabling you to focus on each endpoint's functionality in isolation. Each handler receives the parameters and payload for the request, making it easy to process input and generate a response.
293
502
 
294
- If you need to use services inside your handlers, you can return an
295
- `Effect` from the `HttpApiBuilder.group` api.
503
+ ### Using Services Inside a HttpApiGroup
504
+
505
+ If your handlers need to use services, you can easily integrate them because the `HttpApiBuilder.group` API allows you to return an `Effect`. This ensures that external services can be accessed and utilized directly within your handlers.
506
+
507
+ **Example** (Using Services in a Group Implementation)
296
508
 
297
509
  ```ts
510
+ import {
511
+ HttpApi,
512
+ HttpApiBuilder,
513
+ HttpApiEndpoint,
514
+ HttpApiGroup,
515
+ HttpApiSchema
516
+ } from "@effect/platform"
517
+ import { Context, Effect, Schema } from "effect"
518
+
519
+ class User extends Schema.Class<User>("User")({
520
+ id: Schema.Number,
521
+ name: Schema.String,
522
+ createdAt: Schema.DateTimeUtc
523
+ }) {}
524
+
525
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
526
+
527
+ class UsersApi extends HttpApiGroup.make("users").add(
528
+ HttpApiEndpoint.get("findById")`/users/${UserIdParam}`.addSuccess(User)
529
+ ) {}
530
+
531
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
532
+
533
+ // Define the UsersRepository service
298
534
  class UsersRepository extends Context.Tag("UsersRepository")<
299
535
  UsersRepository,
300
536
  {
@@ -302,14 +538,11 @@ class UsersRepository extends Context.Tag("UsersRepository")<
302
538
  }
303
539
  >() {}
304
540
 
305
- // the dependencies will show up in the resulting `Layer`
306
- const UsersApiLive: Layer.Layer<
307
- HttpApiGroup.ApiGroup<"users">,
308
- never,
309
- UsersRepository
310
- > = HttpApiBuilder.group(MyApi, "users", (handlers) =>
311
- // we can return an Effect that creates our handlers
541
+ // ┌─── Layer<HttpApiGroup.ApiGroup<"myApi", "users">, never, UsersRepository>
542
+ // ▼
543
+ const UsersApiLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
312
544
  Effect.gen(function* () {
545
+ // Access the UsersRepository service
313
546
  const repository = yield* UsersRepository
314
547
  return handlers.handle("findById", ({ path: { userId } }) =>
315
548
  repository.findById(userId)
@@ -318,90 +551,142 @@ const UsersApiLive: Layer.Layer<
318
551
  )
319
552
  ```
320
553
 
321
- ### Implementing a `HttpApi`
554
+ ### Implementing a HttpApi
322
555
 
323
- Once all your groups are implemented, you can implement the top-level `HttpApi`.
556
+ Once all your groups are implemented, you can create a top-level implementation to combine them into a unified API. This is done using the `HttpApiBuilder.api` API, which generates a `Layer`. You then use `Layer.provide` to include the implementations of all the groups into the top-level `HttpApi`.
324
557
 
325
- This is done using the `HttpApiBuilder.api` api, and then using `Layer.provide`
326
- to add all the group implementations.
558
+ **Example** (Combining Group Implementations into a Top-Level API)
327
559
 
328
560
  ```ts
329
- const MyApiLive: Layer.Layer<HttpApi.Api> = HttpApiBuilder.api(MyApi).pipe(
330
- Layer.provide(UsersApiLive)
561
+ import {
562
+ HttpApi,
563
+ HttpApiBuilder,
564
+ HttpApiEndpoint,
565
+ HttpApiGroup,
566
+ HttpApiSchema
567
+ } from "@effect/platform"
568
+ import { DateTime, Effect, Layer, Schema } from "effect"
569
+
570
+ class User extends Schema.Class<User>("User")({
571
+ id: Schema.Number,
572
+ name: Schema.String,
573
+ createdAt: Schema.DateTimeUtc
574
+ }) {}
575
+
576
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
577
+
578
+ class UsersApi extends HttpApiGroup.make("users").add(
579
+ HttpApiEndpoint.get("findById")`/users/${UserIdParam}`.addSuccess(User)
580
+ ) {}
581
+
582
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
583
+
584
+ // --------------------------------------------
585
+ // Implementation
586
+ // --------------------------------------------
587
+
588
+ const UsersApiLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
589
+ handlers.handle("findById", ({ path: { userId } }) =>
590
+ Effect.succeed(
591
+ // Return a mock user object with the provided ID
592
+ new User({
593
+ id: userId,
594
+ name: "John Doe",
595
+ createdAt: DateTime.unsafeNow()
596
+ })
597
+ )
598
+ )
331
599
  )
600
+
601
+ // Combine all group implementations into the top-level API
602
+ //
603
+ // ┌─── Layer<HttpApi.Api, never, never>
604
+ // ▼
605
+ const MyApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(UsersApiLive))
332
606
  ```
333
607
 
334
608
  ### Serving the API
335
609
 
336
- Finally, you can serve the API using the `HttpApiBuilder.serve` api.
610
+ You can serve your API using the `HttpApiBuilder.serve` API. This function builds an `HttpApp` from an `HttpApi` instance and serves it using an `HttpServer`.
611
+
612
+ Optionally, you can provide middleware to enhance the `HttpApp` before serving it.
337
613
 
338
- You can also add middleware to the server using the `HttpMiddleware` module, or
339
- use some of the middleware Layer's from the `HttpApiBuilder` module.
614
+ **Example** (Serving an API with Middleware)
340
615
 
341
616
  ```ts
342
- import { HttpMiddleware, HttpServer } from "@effect/platform"
617
+ import {
618
+ HttpApi,
619
+ HttpApiBuilder,
620
+ HttpApiEndpoint,
621
+ HttpApiGroup,
622
+ HttpApiSchema,
623
+ HttpMiddleware,
624
+ HttpServer
625
+ } from "@effect/platform"
343
626
  import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
627
+ import { DateTime, Effect, Layer, Schema } from "effect"
344
628
  import { createServer } from "node:http"
345
629
 
346
- // use the `HttpApiBuilder.serve` function to register our API with the HTTP
347
- // server
348
- const HttpLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
349
- // Add CORS middleware
350
- Layer.provide(HttpApiBuilder.middlewareCors()),
351
- // Provide the API implementation
352
- Layer.provide(MyApiLive),
353
- // Log the address the server is listening on
354
- HttpServer.withLogAddress,
355
- // Provide the HTTP server implementation
356
- Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
357
- )
630
+ class User extends Schema.Class<User>("User")({
631
+ id: Schema.Number,
632
+ name: Schema.String,
633
+ createdAt: Schema.DateTimeUtc
634
+ }) {}
358
635
 
359
- // run the server
360
- Layer.launch(HttpLive).pipe(NodeRuntime.runMain)
361
- ```
636
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
362
637
 
363
- ### Serving Swagger documentation
638
+ class UsersApi extends HttpApiGroup.make("users").add(
639
+ HttpApiEndpoint.get("findById")`/users/${UserIdParam}`.addSuccess(User)
640
+ ) {}
364
641
 
365
- You can add Swagger documentation to your API using the `HttpApiSwagger` module.
642
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
366
643
 
367
- You just need to provide the `HttpApiSwagger.layer` to your server
368
- implementation:
644
+ const UsersApiLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
645
+ handlers.handle("findById", ({ path: { userId } }) =>
646
+ Effect.succeed(
647
+ new User({
648
+ id: userId,
649
+ name: "John Doe",
650
+ createdAt: DateTime.unsafeNow()
651
+ })
652
+ )
653
+ )
654
+ )
369
655
 
370
- ```ts
371
- import { HttpApiSwagger } from "@effect/platform"
656
+ const MyApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(UsersApiLive))
372
657
 
658
+ // Use the `HttpApiBuilder.serve` function to serve the API
373
659
  const HttpLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
374
- // add the swagger documentation layer
375
- Layer.provide(
376
- HttpApiSwagger.layer({
377
- // "/docs" is the default path for the swagger documentation
378
- path: "/docs"
379
- })
380
- ),
660
+ // Add middleware for Cross-Origin Resource Sharing (CORS)
381
661
  Layer.provide(HttpApiBuilder.middlewareCors()),
662
+ // Provide the API implementation
382
663
  Layer.provide(MyApiLive),
664
+ // Log the server's listening address
665
+ HttpServer.withLogAddress,
666
+ // Provide the HTTP server implementation
383
667
  Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
384
668
  )
669
+
670
+ // run the server
671
+ Layer.launch(HttpLive).pipe(NodeRuntime.runMain)
385
672
  ```
386
673
 
387
- ## Adding middleware
674
+ ## Middlewares
388
675
 
389
- ### Defining middleware
676
+ ### Defining Middleware
390
677
 
391
- The `HttpApiMiddleware` module provides a way to add middleware to your API.
678
+ The `HttpApiMiddleware` module allows you to add middleware to your API. Middleware can enhance your API by introducing features like logging, authentication, or additional error handling.
392
679
 
393
- You can create a `HttpApiMiddleware.Tag` that represents your middleware, which
394
- allows you to set:
680
+ You can define middleware using the `HttpApiMiddleware.Tag` class, which lets you specify:
395
681
 
396
- - `failure` - a Schema for any errors that the middleware can return
397
- - `provides` - a `Context.Tag` that the middleware will provide
398
- - `security` - `HttpApiSecurity` definitions that the middleware will
399
- implement
400
- - `optional` - a boolean that indicates that if the middleware fails with an
401
- expected error, the request should continue. When using optional middleware,
402
- `provides` & `failure` options will not affect the handlers or final error type.
682
+ | Option | Description |
683
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
684
+ | `failure` | A schema that describes any errors the middleware might return. |
685
+ | `provides` | A `Context.Tag` representing the resource or data the middleware will provide to subsequent handlers. |
686
+ | `security` | Definitions from `HttpApiSecurity` that the middleware will implement, such as authentication mechanisms. |
687
+ | `optional` | A boolean indicating whether the request should continue if the middleware fails with an expected error. When `optional` is set to `true`, the `provides` and `failure` options do not affect the final error type or handlers. |
403
688
 
404
- Here is an example of defining a simple logger middleware:
689
+ **Example** (Defining a Logger Middleware)
405
690
 
406
691
  ```ts
407
692
  import {
@@ -411,42 +696,126 @@ import {
411
696
  } from "@effect/platform"
412
697
  import { Schema } from "effect"
413
698
 
699
+ // Define a schema for errors returned by the logger middleware
414
700
  class LoggerError extends Schema.TaggedError<LoggerError>()(
415
701
  "LoggerError",
416
702
  {}
417
703
  ) {}
418
704
 
419
- // first extend the HttpApiMiddleware.Tag class
705
+ // Extend the HttpApiMiddleware.Tag class to define the logger middleware tag
420
706
  class Logger extends HttpApiMiddleware.Tag<Logger>()("Http/Logger", {
421
- // optionally define any errors that the middleware can return
707
+ // Optionally define the error schema for the middleware
422
708
  failure: LoggerError
423
709
  }) {}
424
710
 
425
- // apply the middleware to an `HttpApiGroup`
426
711
  class UsersApi extends HttpApiGroup.make("users")
427
712
  .add(
428
713
  HttpApiEndpoint.get("findById")`/${Schema.NumberFromString}`
429
- // apply the middleware to a single endpoint
714
+ // Apply the middleware to a single endpoint
430
715
  .middleware(Logger)
431
716
  )
432
- // or apply the middleware to the group
717
+ // Or apply the middleware to the entire group
433
718
  .middleware(Logger) {}
434
719
  ```
435
720
 
721
+ ### Implementing HttpApiMiddleware
722
+
723
+ Once you have defined your `HttpApiMiddleware`, you can implement it as a `Layer`. This allows the middleware to be applied to specific API groups or endpoints, enabling modular and reusable behavior.
724
+
725
+ **Example** (Implementing and Using Logger Middleware)
726
+
727
+ ```ts
728
+ import { HttpApiMiddleware, HttpServerRequest } from "@effect/platform"
729
+ import { Effect, Layer } from "effect"
730
+
731
+ class Logger extends HttpApiMiddleware.Tag<Logger>()("Http/Logger") {}
732
+
733
+ const LoggerLive = Layer.effect(
734
+ Logger,
735
+ Effect.gen(function* () {
736
+ yield* Effect.log("creating Logger middleware")
737
+
738
+ // Middleware implementation as an Effect
739
+ // that can access the `HttpServerRequest` context.
740
+ return Effect.gen(function* () {
741
+ const request = yield* HttpServerRequest.HttpServerRequest
742
+ yield* Effect.log(`Request: ${request.method} ${request.url}`)
743
+ })
744
+ })
745
+ )
746
+ ```
747
+
748
+ After implementing the middleware, you can attach it to your API groups or specific endpoints using the `Layer` APIs.
749
+
750
+ ```ts
751
+ import {
752
+ HttpApi,
753
+ HttpApiBuilder,
754
+ HttpApiEndpoint,
755
+ HttpApiGroup,
756
+ HttpApiMiddleware,
757
+ HttpServerRequest
758
+ } from "@effect/platform"
759
+ import { DateTime, Effect, Layer, Schema } from "effect"
760
+
761
+ class Logger extends HttpApiMiddleware.Tag<Logger>()("Http/Logger") {}
762
+
763
+ const LoggerLive = Layer.effect(
764
+ Logger,
765
+ Effect.gen(function* () {
766
+ yield* Effect.log("creating Logger middleware")
767
+ return Effect.gen(function* () {
768
+ const request = yield* HttpServerRequest.HttpServerRequest
769
+ yield* Effect.log(`Request: ${request.method} ${request.url}`)
770
+ })
771
+ })
772
+ )
773
+
774
+ class UsersApi extends HttpApiGroup.make("users").add(
775
+ HttpApiEndpoint.get("findById")`/${Schema.NumberFromString}`.middleware(
776
+ Logger
777
+ )
778
+ ) {}
779
+
780
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
781
+
782
+ class User extends Schema.Class<User>("User")({
783
+ id: Schema.Number,
784
+ name: Schema.String,
785
+ createdAt: Schema.DateTimeUtc
786
+ }) {}
787
+
788
+ const UsersApiLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
789
+ handlers.handle("findById", (req) =>
790
+ Effect.succeed(
791
+ new User({
792
+ id: req.path[0],
793
+ name: "John Doe",
794
+ createdAt: DateTime.unsafeNow()
795
+ })
796
+ )
797
+ )
798
+ ).pipe(
799
+ // Provide the Logger middleware to the group
800
+ Layer.provide(LoggerLive)
801
+ )
802
+ ```
803
+
436
804
  ### Defining security middleware
437
805
 
438
- The `HttpApiSecurity` module provides a way to add security annotations to your
439
- API.
806
+ The `HttpApiSecurity` module enables you to add security annotations to your API. These annotations specify the type of authorization required to access specific endpoints.
807
+
808
+ Supported authorization types include:
440
809
 
441
- It offers the following authorization types:
810
+ | Authorization Type | Description |
811
+ | ------------------------ | ---------------------------------------------------------------- |
812
+ | `HttpApiSecurity.apiKey` | API key authorization via headers, query parameters, or cookies. |
813
+ | `HttpApiSecurity.basic` | HTTP Basic authentication. |
814
+ | `HttpApiSecurity.bearer` | Bearer token authentication. |
442
815
 
443
- - `HttpApiSecurity.apiKey` - API key authorization through headers, query
444
- parameters, or cookies.
445
- - `HttpApiSecurity.basicAuth` - HTTP Basic authentication.
446
- - `HttpApiSecurity.bearerAuth` - Bearer token authentication.
816
+ These security annotations can be used alongside `HttpApiMiddleware` to create middleware that protects your API endpoints.
447
817
 
448
- You can then use these security annotations in combination with `HttpApiMiddleware`
449
- to define middleware that will protect your endpoints.
818
+ **Example** (Defining Security Middleware)
450
819
 
451
820
  ```ts
452
821
  import {
@@ -458,92 +827,54 @@ import {
458
827
  } from "@effect/platform"
459
828
  import { Context, Schema } from "effect"
460
829
 
830
+ // Define a schema for the "User"
461
831
  class User extends Schema.Class<User>("User")({ id: Schema.Number }) {}
462
832
 
833
+ // Define a schema for the "Unauthorized" error
463
834
  class Unauthorized extends Schema.TaggedError<Unauthorized>()(
464
835
  "Unauthorized",
465
836
  {},
837
+ // Specify the HTTP status code for unauthorized errors
466
838
  HttpApiSchema.annotations({ status: 401 })
467
839
  ) {}
468
840
 
841
+ // Define a Context.Tag for the authenticated user
469
842
  class CurrentUser extends Context.Tag("CurrentUser")<CurrentUser, User>() {}
470
843
 
471
- // first extend the HttpApiMiddleware.Tag class
844
+ // Create the Authorization middleware
472
845
  class Authorization extends HttpApiMiddleware.Tag<Authorization>()(
473
846
  "Authorization",
474
847
  {
475
- // add your error schema
848
+ // Define the error schema for unauthorized access
476
849
  failure: Unauthorized,
477
- // add the Context.Tag that the middleware will provide
850
+ // Specify the resource this middleware will provide
478
851
  provides: CurrentUser,
479
- // add the security definitions
852
+ // Add security definitions
480
853
  security: {
481
- // the object key is a custom name for the security definition
854
+ // ┌─── Custom name for the security definition
855
+ // ▼
482
856
  myBearer: HttpApiSecurity.bearer
483
- // You can add more security definitions here.
484
- // They will attempt to be resolved in the order they are defined
857
+ // Additional security definitions can be added here.
858
+ // They will attempt to be resolved in the order they are defined.
485
859
  }
486
860
  }
487
861
  ) {}
488
862
 
489
- // apply the middleware to an `HttpApiGroup`
490
863
  class UsersApi extends HttpApiGroup.make("users")
491
864
  .add(
492
865
  HttpApiEndpoint.get("findById")`/${Schema.NumberFromString}`
493
- // apply the middleware to a single endpoint
866
+ // Apply the middleware to a single endpoint
494
867
  .middleware(Authorization)
495
868
  )
496
- // or apply the middleware to the group
869
+ // Or apply the middleware to the entire group
497
870
  .middleware(Authorization) {}
498
871
  ```
499
872
 
500
- ### Implementing `HttpApiMiddleware`
501
-
502
- Once your `HttpApiMiddleware` is defined, you can use the
503
- `HttpApiMiddleware.Tag` definition to implement your middleware.
504
-
505
- By using the `Layer` apis, you can create a Layer that implements your
506
- middleware.
507
-
508
- Here is an example:
509
-
510
- ```ts
511
- import { HttpApiMiddleware, HttpServerRequest } from "@effect/platform"
512
- import { Effect, Layer } from "effect"
513
-
514
- class Logger extends HttpApiMiddleware.Tag<Logger>()("Http/Logger") {}
515
-
516
- const LoggerLive = Layer.effect(
517
- Logger,
518
- Effect.gen(function* () {
519
- yield* Effect.log("creating Logger middleware")
520
-
521
- // standard middleware is just an Effect, that can access the `HttpRouter`
522
- // context.
523
- return Logger.of(
524
- Effect.gen(function* () {
525
- const request = yield* HttpServerRequest.HttpServerRequest
526
- yield* Effect.log(`Request: ${request.method} ${request.url}`)
527
- })
528
- )
529
- })
530
- )
531
- ```
532
-
533
- When the `Layer` is created, you can then provide it to your group layers:
534
-
535
- ```ts
536
- const UsersApiLive = HttpApiBuilder.group(...).pipe(
537
- Layer.provide(LoggerLive)
538
- )
539
- ```
540
-
541
- ### Implementing `HttpApiSecurity` middleware
873
+ ### Implementing HttpApiSecurity middleware
542
874
 
543
- If you are using `HttpApiSecurity` in your middleware, implementing the `Layer`
544
- looks a bit different.
875
+ When using `HttpApiSecurity` in your middleware, the implementation involves creating a `Layer` with security handlers tailored to your requirements. Below is an example demonstrating how to implement middleware for `HttpApiSecurity.bearer` authentication.
545
876
 
546
- Here is an example of implementing a `HttpApiSecurity.bearer` middleware:
877
+ **Example** (Implementing Bearer Token Authentication Middleware)
547
878
 
548
879
  ```ts
549
880
  import {
@@ -577,62 +908,240 @@ const AuthorizationLive = Layer.effect(
577
908
  Effect.gen(function* () {
578
909
  yield* Effect.log("creating Authorization middleware")
579
910
 
580
- // return the security handlers
581
- return Authorization.of({
911
+ // Return the security handlers for the middleware
912
+ return {
913
+ // Define the handler for the Bearer token
914
+ // The Bearer token is redacted for security
582
915
  myBearer: (bearerToken) =>
583
916
  Effect.gen(function* () {
584
917
  yield* Effect.log(
585
918
  "checking bearer token",
586
919
  Redacted.value(bearerToken)
587
920
  )
588
- // return the `User` that will be provided as the `CurrentUser`
921
+ // Return a mock User object as the CurrentUser
589
922
  return new User({ id: 1 })
590
923
  })
591
- })
924
+ }
592
925
  })
593
926
  )
594
927
  ```
595
928
 
596
- ### Setting `HttpApiSecurity` cookies
929
+ ### Setting HttpApiSecurity cookies
597
930
 
598
- If you need to set the security cookie from within a handler, you can use the
599
- `HttpApiBuilder.securitySetCookie` api.
931
+ To set a security cookie from within a handler, you can use the `HttpApiBuilder.securitySetCookie` API. This method sets a cookie with default properties, including the `HttpOnly` and `Secure` flags, ensuring the cookie is not accessible via JavaScript and is transmitted over secure connections.
600
932
 
601
- By default, the cookie will be set with the `HttpOnly` and `Secure` flags.
933
+ **Example** (Setting a Security Cookie in a Login Handler)
602
934
 
603
935
  ```ts
936
+ // Define the security configuration for an API key stored in a cookie
604
937
  const security = HttpApiSecurity.apiKey({
605
- in: "cookie",
938
+ // Specify that the API key is stored in a cookie
939
+ in: "cookie"
940
+ // Define the cookie name,
606
941
  key: "token"
607
942
  })
608
943
 
609
944
  const UsersApiLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
610
945
  handlers.handle("login", () =>
611
- // set the security cookie
946
+ // Set the security cookie with a redacted value
612
947
  HttpApiBuilder.securitySetCookie(security, Redacted.make("keep me secret"))
613
948
  )
614
949
  )
615
950
  ```
616
951
 
617
- ## Deriving a client
952
+ ## Serving Swagger documentation
618
953
 
619
- Once you have defined your API, you can derive a client that can interact with
620
- the server.
954
+ You can add Swagger documentation to your API using the `HttpApiSwagger` module. This integration provides an interactive interface for developers to explore and test your API. To enable Swagger, you simply provide the `HttpApiSwagger.layer` to your server implementation.
621
955
 
622
- The `HttpApiClient` module provides all the apis you need to derive a client.
956
+ **Example** (Adding Swagger Documentation to an API)
623
957
 
624
958
  ```ts
625
- import { HttpApiClient } from "@effect/platform"
959
+ import {
960
+ HttpApi,
961
+ HttpApiBuilder,
962
+ HttpApiEndpoint,
963
+ HttpApiGroup,
964
+ HttpApiSchema,
965
+ HttpApiSwagger,
966
+ HttpMiddleware,
967
+ HttpServer
968
+ } from "@effect/platform"
969
+ import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
970
+ import { DateTime, Effect, Layer, Schema } from "effect"
971
+ import { createServer } from "node:http"
626
972
 
627
- Effect.gen(function* () {
973
+ class User extends Schema.Class<User>("User")({
974
+ id: Schema.Number,
975
+ name: Schema.String,
976
+ createdAt: Schema.DateTimeUtc
977
+ }) {}
978
+
979
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
980
+
981
+ class UsersApi extends HttpApiGroup.make("users").add(
982
+ HttpApiEndpoint.get("findById")`/users/${UserIdParam}`.addSuccess(User)
983
+ ) {}
984
+
985
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
986
+
987
+ const UsersApiLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
988
+ handlers.handle("findById", ({ path: { userId } }) =>
989
+ Effect.succeed(
990
+ new User({
991
+ id: userId,
992
+ name: "John Doe",
993
+ createdAt: DateTime.unsafeNow()
994
+ })
995
+ )
996
+ )
997
+ )
998
+
999
+ const MyApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(UsersApiLive))
1000
+
1001
+ const HttpLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
1002
+ // Add the Swagger documentation layer
1003
+ Layer.provide(
1004
+ HttpApiSwagger.layer({
1005
+ // Specify the Swagger documentation path.
1006
+ // "/docs" is the default path.
1007
+ path: "/docs"
1008
+ })
1009
+ ),
1010
+ Layer.provide(HttpApiBuilder.middlewareCors()),
1011
+ Layer.provide(MyApiLive),
1012
+ HttpServer.withLogAddress,
1013
+ Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
1014
+ )
1015
+
1016
+ Layer.launch(HttpLive).pipe(NodeRuntime.runMain)
1017
+ ```
1018
+
1019
+ ![Swagger Documentation](./images/swagger-myapi.png)
1020
+
1021
+ ### Adding OpenAPI Annotations
1022
+
1023
+ You can enhance your API documentation by adding OpenAPI annotations using the `OpenApi` module. These annotations allow you to include metadata such as titles, descriptions, and other details, making your API documentation more informative and easier to use.
1024
+
1025
+ **Example** (Adding OpenAPI Annotations to a Group)
1026
+
1027
+ In this example:
1028
+
1029
+ - A title ("Users API") and description ("API for managing users") are added to the `UsersApi` group.
1030
+ - These annotations will appear in the generated OpenAPI documentation.
1031
+
1032
+ ```ts
1033
+ import { OpenApi } from "@effect/platform"
1034
+
1035
+ class UsersApi extends HttpApiGroup.make("users").add(
1036
+ HttpApiEndpoint.get("findById")`/users/${UserIdParam}`
1037
+ .addSuccess(User)
1038
+ // You can set one attribute at a time
1039
+ .annotate(OpenApi.Title, "Users API")
1040
+ // or multiple at once
1041
+ .annotateContext(
1042
+ OpenApi.annotations({
1043
+ title: "Users API",
1044
+ description: "API for managing users"
1045
+ })
1046
+ )
1047
+ ) {}
1048
+ ```
1049
+
1050
+ Annotations can also be applied to the entire API. In this example, a title ("My API") is added to the top-level `HttpApi`.
1051
+
1052
+ **Example** (Adding OpenAPI Annotations to the Top-Level API)
1053
+
1054
+ ```ts
1055
+ class MyApi extends HttpApi.make("myApi")
1056
+ .add(UsersApi)
1057
+ // Add a title for the top-level API
1058
+ .annotate(OpenApi.Title, "My API") {}
1059
+ ```
1060
+
1061
+ ## Deriving a Client
1062
+
1063
+ After defining your API, you can derive a client that interacts with the server. The `HttpApiClient` module simplifies the process by providing tools to generate a client based on your API definition.
1064
+
1065
+ **Example** (Deriving and Using a Client)
1066
+
1067
+ This example demonstrates how to create a client for an API and use it to call an endpoint.
1068
+
1069
+ ```ts
1070
+ import {
1071
+ FetchHttpClient,
1072
+ HttpApi,
1073
+ HttpApiBuilder,
1074
+ HttpApiClient,
1075
+ HttpApiEndpoint,
1076
+ HttpApiGroup,
1077
+ HttpApiSchema,
1078
+ HttpApiSwagger,
1079
+ HttpMiddleware,
1080
+ HttpServer
1081
+ } from "@effect/platform"
1082
+ import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
1083
+ import { DateTime, Effect, Layer, Schema } from "effect"
1084
+ import { createServer } from "node:http"
1085
+
1086
+ class User extends Schema.Class<User>("User")({
1087
+ id: Schema.Number,
1088
+ name: Schema.String,
1089
+ createdAt: Schema.DateTimeUtc
1090
+ }) {}
1091
+
1092
+ const UserIdParam = HttpApiSchema.param("userId", Schema.NumberFromString)
1093
+
1094
+ class UsersApi extends HttpApiGroup.make("users").add(
1095
+ HttpApiEndpoint.get("findById")`/users/${UserIdParam}`.addSuccess(User)
1096
+ ) {}
1097
+
1098
+ class MyApi extends HttpApi.make("myApi").add(UsersApi) {}
1099
+
1100
+ const UsersApiLive = HttpApiBuilder.group(MyApi, "users", (handlers) =>
1101
+ handlers.handle("findById", ({ path: { userId } }) =>
1102
+ Effect.succeed(
1103
+ new User({
1104
+ id: userId,
1105
+ name: "John Doe",
1106
+ createdAt: DateTime.unsafeNow()
1107
+ })
1108
+ )
1109
+ )
1110
+ )
1111
+
1112
+ const MyApiLive = HttpApiBuilder.api(MyApi).pipe(Layer.provide(UsersApiLive))
1113
+
1114
+ const HttpLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
1115
+ Layer.provide(HttpApiSwagger.layer()),
1116
+ Layer.provide(HttpApiBuilder.middlewareCors()),
1117
+ Layer.provide(MyApiLive),
1118
+ HttpServer.withLogAddress,
1119
+ Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
1120
+ )
1121
+
1122
+ Layer.launch(HttpLive).pipe(NodeRuntime.runMain)
1123
+
1124
+ // Create a program that derives and uses the client
1125
+ const program = Effect.gen(function* () {
1126
+ // Derive the client
628
1127
  const client = yield* HttpApiClient.make(MyApi, {
629
1128
  baseUrl: "http://localhost:3000"
630
- // You can transform the HttpClient to add things like authentication
631
- // transformClient: ....
632
1129
  })
1130
+ // Call the `findById` endpoint
633
1131
  const user = yield* client.users.findById({ path: { userId: 1 } })
634
- yield* Effect.log(user)
1132
+ console.log(user)
635
1133
  })
1134
+
1135
+ // Provide a Fetch-based HTTP client and run the program
1136
+ Effect.runFork(program.pipe(Effect.provide(FetchHttpClient.layer)))
1137
+ /*
1138
+ Example Output:
1139
+ User {
1140
+ id: 1,
1141
+ name: 'John Doe',
1142
+ createdAt: DateTime.Utc(2025-01-04T15:14:49.562Z)
1143
+ }
1144
+ */
636
1145
  ```
637
1146
 
638
1147
  # HTTP Client
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/platform",
3
- "version": "0.72.0",
3
+ "version": "0.72.1",
4
4
  "description": "Unified interfaces for common platform-specific services",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -14,7 +14,7 @@
14
14
  "multipasta": "^0.2.5"
15
15
  },
16
16
  "peerDependencies": {
17
- "effect": "^3.12.0"
17
+ "effect": "^3.12.1"
18
18
  },
19
19
  "publishConfig": {
20
20
  "provenance": true