effect-http-server
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStructure
结构
HttpApi
├── HttpApiGroup
│ ├── HttpApiEndpoint
│ └── HttpApiEndpointHttpApi
├── HttpApiGroup
│ ├── HttpApiEndpoint
│ └── HttpApiEndpointDefining Endpoints
定义端点
ts
import { HttpApiEndpoint, HttpApiSchema, HttpApiError } from "@effect/platform"
import { Schema } from "effect"
const User = Schema.Struct({
id: Schema.Number,
name: Schema.String,
createdAt: Schema.DateTimeUtc
})
const idParam = HttpApiSchema.param("id", Schema.NumberFromString)
// GET with path param (template string syntax)
const getUser = HttpApiEndpoint.get("getUser")`/users/${idParam}`
.addSuccess(User)
.addError(HttpApiError.NotFound) // 404
// GET with URL params
const listUsers = HttpApiEndpoint.get("listUsers", "/users")
.setUrlParams(Schema.Struct({
page: Schema.NumberFromString,
sort: Schema.String
}))
.addSuccess(Schema.Array(User))
// POST with payload
const createUser = HttpApiEndpoint.post("createUser", "/users")
.setPayload(Schema.Struct({ name: Schema.String }))
.addSuccess(User, { status: 201 })
// DELETE
const deleteUser = HttpApiEndpoint.del("deleteUser")`/users/${idParam}`
// PATCH
const updateUser = HttpApiEndpoint.patch("updateUser")`/users/${idParam}`
.setPayload(Schema.Struct({ name: Schema.String }))
.addSuccess(User)
// Headers (keys must be lowercase)
const withHeaders = HttpApiEndpoint.get("withHeaders", "/")
.setHeaders(Schema.Struct({
"x-api-key": Schema.String,
"x-request-id": Schema.String
}))ts
import { HttpApiEndpoint, HttpApiSchema, HttpApiError } from "@effect/platform"
import { Schema } from "effect"
const User = Schema.Struct({
id: Schema.Number,
name: Schema.String,
createdAt: Schema.DateTimeUtc
})
const idParam = HttpApiSchema.param("id", Schema.NumberFromString)
// GET with path param (template string syntax)
const getUser = HttpApiEndpoint.get("getUser")`/users/${idParam}`
.addSuccess(User)
.addError(HttpApiError.NotFound) // 404
// GET with URL params
const listUsers = HttpApiEndpoint.get("listUsers", "/users")
.setUrlParams(Schema.Struct({
page: Schema.NumberFromString,
sort: Schema.String
}))
.addSuccess(Schema.Array(User))
// POST with payload
const createUser = HttpApiEndpoint.post("createUser", "/users")
.setPayload(Schema.Struct({ name: Schema.String }))
.addSuccess(User, { status: 201 })
// DELETE
const deleteUser = HttpApiEndpoint.del("deleteUser")`/users/${idParam}`
// PATCH
const updateUser = HttpApiEndpoint.patch("updateUser")`/users/${idParam}`
.setPayload(Schema.Struct({ name: Schema.String }))
.addSuccess(User)
// Headers (keys must be lowercase)
const withHeaders = HttpApiEndpoint.get("withHeaders", "/")
.setHeaders(Schema.Struct({
"x-api-key": Schema.String,
"x-request-id": Schema.String
}))Grouping & API Assembly
分组与API组装
ts
import { HttpApi, HttpApiGroup } from "@effect/platform"
class UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", {}) {}
class Unauthorized extends Schema.TaggedError<Unauthorized>()("Unauthorized", {}) {}
const usersGroup = HttpApiGroup.make("users")
.add(getUser)
.add(listUsers)
.add(createUser)
.add(deleteUser)
.add(updateUser)
.addError(Unauthorized, { status: 401 }) // group-level error
const api = HttpApi.make("myApi")
.add(usersGroup)
.prefix("/api/v1") // optional prefixts
import { HttpApi, HttpApiGroup } from "@effect/platform"
class UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", {}) {}
class Unauthorized extends Schema.TaggedError<Unauthorized>()("Unauthorized", {}) {}
const usersGroup = HttpApiGroup.make("users")
.add(getUser)
.add(listUsers)
.add(createUser)
.add(deleteUser)
.add(updateUser)
.addError(Unauthorized, { status: 401 }) // group-level error
const api = HttpApi.make("myApi")
.add(usersGroup)
.prefix("/api/v1") // optional prefixImplementation
实现
ts
import { HttpApiBuilder } from "@effect/platform"
import { Context, Effect, Layer, DateTime } from "effect"
// Service for handlers
class UsersRepo extends Context.Tag("UsersRepo")<UsersRepo, {
findById: (id: number) => Effect.Effect<typeof User.Type>
}>() {}
// Implement group handlers
const usersGroupLive = HttpApiBuilder.group(api, "users", (handlers) =>
Effect.gen(function* () {
const repo = yield* UsersRepo
return handlers
.handle("getUser", ({ path: { id } }) => repo.findById(id))
.handle("listUsers", ({ urlParams: { page, sort } }) =>
Effect.succeed([{ id: 1, name: "John", createdAt: DateTime.unsafeNow() }])
)
.handle("createUser", ({ payload: { name } }) =>
Effect.succeed({ id: 2, name, createdAt: DateTime.unsafeNow() })
)
.handle("deleteUser", ({ path: { id } }) => Effect.void)
.handle("updateUser", ({ path: { id }, payload: { name } }) =>
Effect.succeed({ id, name, createdAt: DateTime.unsafeNow() })
)
// Access raw request if needed (do not echo secrets)
.handle("withHeaders", ({ request }) =>
Effect.succeed(`method: ${request.method}`)
)
})
)
// Combine into API layer
const MyApiLive = HttpApiBuilder.api(api).pipe(Layer.provide(usersGroupLive))ts
import { HttpApiBuilder } from "@effect/platform"
import { Context, Effect, Layer, DateTime } from "effect"
// Service for handlers
class UsersRepo extends Context.Tag("UsersRepo")<UsersRepo, {
findById: (id: number) => Effect.Effect<typeof User.Type>
}>() {}
// Implement group handlers
const usersGroupLive = HttpApiBuilder.group(api, "users", (handlers) =>
Effect.gen(function* () {
const repo = yield* UsersRepo
return handlers
.handle("getUser", ({ path: { id } }) => repo.findById(id))
.handle("listUsers", ({ urlParams: { page, sort } }) =>
Effect.succeed([{ id: 1, name: "John", createdAt: DateTime.unsafeNow() }])
)
.handle("createUser", ({ payload: { name } }) =>
Effect.succeed({ id: 2, name, createdAt: DateTime.unsafeNow() })
)
.handle("deleteUser", ({ path: { id } }) => Effect.void)
.handle("updateUser", ({ path: { id }, payload: { name } }) =>
Effect.succeed({ id, name, createdAt: DateTime.unsafeNow() })
)
// Access raw request if needed (do not echo secrets)
.handle("withHeaders", ({ request }) =>
Effect.succeed(`method: ${request.method}`)
)
})
)
// Combine into API layer
const MyApiLive = HttpApiBuilder.api(api).pipe(Layer.provide(usersGroupLive))Serving
服务启动
ts
import { HttpApiSwagger, HttpMiddleware, HttpServer } from "@effect/platform"
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { createServer } from "node:http"
const ServerLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
Layer.provide(HttpApiSwagger.layer()), // /docs
Layer.provide(HttpApiBuilder.middlewareCors()), // CORS
Layer.provide(MyApiLive),
HttpServer.withLogAddress,
Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)
Layer.launch(ServerLive).pipe(NodeRuntime.runMain)ts
import { HttpApiSwagger, HttpMiddleware, HttpServer } from "@effect/platform"
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { createServer } from "node:http"
const ServerLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
Layer.provide(HttpApiSwagger.layer()), // /docs
Layer.provide(HttpApiBuilder.middlewareCors()), // CORS
Layer.provide(MyApiLive),
HttpServer.withLogAddress,
Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)
Layer.launch(ServerLive).pipe(NodeRuntime.runMain)Client
客户端
ts
import { HttpApiClient, FetchHttpClient } from "@effect/platform"
const program = Effect.gen(function* () {
const client = yield* HttpApiClient.make(api, { baseUrl: "http://localhost:3000" })
const user = yield* client.users.getUser({ path: { id: 1 } })
})
program.pipe(Effect.provide(FetchHttpClient.layer), Effect.runPromise)ts
import { HttpApiClient, FetchHttpClient } from "@effect/platform"
const program = Effect.gen(function* () {
const client = yield* HttpApiClient.make(api, { baseUrl: "http://localhost:3000" })
const user = yield* client.users.getUser({ path: { id: 1 } })
})
program.pipe(Effect.provide(FetchHttpClient.layer), Effect.runPromise)Custom Encoding
自定义编码
ts
// URL-encoded request
const urlEncoded = HttpApiEndpoint.post("urlEncoded", "/form")
.setPayload(
Schema.Struct({ a: Schema.String })
.pipe(HttpApiSchema.withEncoding({ kind: "UrlParams" }))
)
// CSV response
const csv = HttpApiEndpoint.get("csv", "/export")
.addSuccess(
Schema.String.pipe(HttpApiSchema.withEncoding({
kind: "Text",
contentType: "text/csv"
}))
)ts
// URL-encoded request
const urlEncoded = HttpApiEndpoint.post("urlEncoded", "/form")
.setPayload(
Schema.Struct({ a: Schema.String })
.pipe(HttpApiSchema.withEncoding({ kind: "UrlParams" }))
)
// CSV response
const csv = HttpApiEndpoint.get("csv", "/export")
.addSuccess(
Schema.String.pipe(HttpApiSchema.withEncoding({
kind: "Text",
contentType: "text/csv"
}))
)Predefined Errors
预定义错误
| Error | Status |
|---|---|
| 400 |
| 401 |
| 403 |
| 404 |
| 409 |
| 500 |
| 错误 | 状态码 |
|---|---|
| 400 |
| 401 |
| 403 |
| 404 |
| 409 |
| 500 |
Additional Resources
额外资源
For deriving http clients
For creating middlewares
For deriving swagger UIs
For multipart uploads
For streaming
生成HTTP客户端
创建中间件
生成Swagger UI
多部分上传
流处理