effect-http-server

📁 tstelzer/skills 📅 12 days ago
30
总安装量
30
周安装量
#12244
全站排名
安装命令
npx skills add https://github.com/tstelzer/skills --skill effect-http-server

Agent 安装分布

cursor 30
opencode 28
codex 28
claude-code 26
github-copilot 26
gemini-cli 24

Skill 文档

Structure

HttpApi
├── HttpApiGroup
│   ├── HttpApiEndpoint
│   └── HttpApiEndpoint

Defining Endpoints

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

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 prefix

Implementation

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

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

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

// 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
HttpApiError.BadRequest 400
HttpApiError.Unauthorized 401
HttpApiError.Forbidden 403
HttpApiError.NotFound 404
HttpApiError.Conflict 409
HttpApiError.InternalServerError 500

Additional Resources

For deriving http clients For creating middlewares For deriving swagger UIs For multipart uploads For streaming