1---
2title: HTTP routes
3description: Compose Git route layers beside your application's Effect HTTP API, with your middleware and server.
4---
5
6Your application owns its `HttpApi`, middleware, groups, and server. Git supplies
7schemas, default handler functions, and operations you can call from your own
8implementations.
9
10## Add Git to your application
11
12Build groups against the API that actually serves them:
13
14```typescript
15import * as Git from "alchemy/Git";
16import * as Effect from "effect/Effect";
17import * as Layer from "effect/Layer";
18import * as HttpApi from "effect/http-api/HttpApi";
19import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
20import { Authentication, CurrentUser } from "./authentication.ts";
21import { AppRoutes } from "./app-routes.ts";
22import { receivePack } from "./receive-pack.ts";
23
24class AppApi extends HttpApi.make("app")
25 .add(Git.Repos)
26 .add(Git.Protocol)
27 .add(AppRoutes)
28 .middleware(Authentication) {}
29
30const MeLive = HttpApiBuilder.group(AppApi, "app", h =>
31 h.handle("me", () => CurrentUser),
32);
33const ReposLive = HttpApiBuilder.group(AppApi, "repos", h =>
34 Effect.map(Git.Handlers, defaults => h.handleAll(defaults.repos)),
35);
36const ProtocolLive = HttpApiBuilder.group(AppApi, "protocol", h =>
37 Effect.map(Git.Handlers, defaults => h
38 .handleRaw("infoRefs", defaults.protocol.infoRefs)
39 .handleRaw("uploadPack", defaults.protocol.uploadPack)
40 .handleRaw("receivePack", receivePack)),
41);
42const PublicRoutes = HttpApiBuilder.layer(AppApi).pipe(
43 Layer.provide(Layer.mergeAll(ReposLive, ProtocolLive, MeLive)),
44 Layer.provide(Authentication.layer),
45);
46```
47
48Here `Authentication` is native `HttpApiMiddleware` providing the application's
49user service. All three groups use this same `AppApi`. Capture infrastructure services
50while constructing groups, and read the request user inside each handler.
51
52`receivePack` is your application's implementation. It decodes the request,
53authorizes changes, prepares the push, validates content, and commits through
54`Git.Engine`. See [Engine operations](/git/blocks/engine) and the [complete
55example](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-git-service/src/api).
56
57Use `addHttpApi(Git.Api)` if you want all six Git groups, then implement each group
58against `AppApi`. `Git.Handlers` supplies default functions for every group.
59The engine does not receive your API or application layers.
60
61## Use the default API
62
63For an unchanged Git API, the supplied layers keep the assembly short:
64
65```typescript
66const PublicRoutes = Layer.mergeAll(AppApiLive, Git.ApiLive).pipe(
67 Layer.provide(RouterAuthentication.layer),
68);
69```
70
71`RouterAuthentication` here is native `HttpRouter.middleware`, as used by the
72tutorial. It applies to both route layers. `Git.ApiLive` registers defaults
73against `Git.Api`; adding middleware to a different API does not modify them.
74
75## Define an application endpoint
76
77```typescript
78import * as Effect from "effect/Effect";
79import * as HttpApi from "effect/http-api/HttpApi";
80import * as HttpApiEndpoint from "effect/http-api/HttpApiEndpoint";
81import * as HttpApiGroup from "effect/http-api/HttpApiGroup";
82
83class AppRoutes extends HttpApiGroup.make("app").add(
84 HttpApiEndpoint.get("me", "/api/v1/me", {
85 success: User,
86 error: Unauthorized,
87 }),
88) {}
89class AppApi extends HttpApi.make("app").add(AppRoutes) {}
90
91const MeLive = HttpApiBuilder.group(AppApi, "app", (h) =>
92 h.handle("me", () => Effect.gen(function* () {
93 const { user } = yield* Session;
94 if (user === null) return yield* new Unauthorized();
95 return user;
96 })),
97);
98```
99
100The application schema is independent of Git. To describe both APIs to a client
101or OpenAPI generator, compose their schemas with native `addHttpApi`:
102
103```typescript
104class CombinedApi extends AppApi.addHttpApi(Git.Api) {}
105```
106
107Schema composition does not register routes or apply middleware to already-built
108route layers. Build the groups against `CombinedApi` to use its middleware and paths.
109
110## Build the HTTP server
111
112```typescript
113import * as Http from "alchemy/Http";
114import * as HttpRouter from "effect/http/HttpRouter";
115
116const HttpLive = Layer.mergeAll(PublicRoutes, Git.InternalApiLive).pipe(
117 Layer.provide(Git.ApiHandlersLive),
118 Layer.provide(Git.ReposDurableObject),
119 Layer.provide(Git.RegistryDurableObject),
120 Layer.provide(Git.HasherInline),
121 Layer.provide(Git.BlobStoreR2(GitObjects)),
122 Layer.provide(Http.Platform),
123);
124
125const fetch = yield* HttpRouter.toHttpEffect(HttpLive);
126return { fetch };
127```
128
129`Http.Platform` provides the platform services needed on a Worker or Lambda.
130The application chooses CORS and any other middleware with Effect's HTTP APIs.
131`Git.ApiHandlersLive` provides `Git.Engine`, the shared operations/cache, and
132default HTTP adapters. `Git.EngineLive` supplies operations without HTTP handlers.
133
134## Replace a Git handler
135
136Most applications use `Git.ApiLive`. For an override, use the native API builder
137and merge your replacement group after the default `Git.GroupsLive`:
138
139```typescript
140const GitHubLive = HttpApiBuilder.group(Git.Api, "github", (h) =>
141 Effect.map(Git.Handlers, (git) => h.handleAll({
142 ...git.github,
143 user: () => Effect.gen(function* () {
144 const { user } = yield* Session;
145 return HttpServerResponse.jsonUnsafe({ login: user?.id });
146 }),
147 })),
148);
149
150const CustomGitRoutes = HttpApiBuilder.layer(Git.Api).pipe(
151 Layer.provide(Layer.mergeAll(Git.GroupsLive, GitHubLive)),
152);
153const PublicRoutes = Layer.mergeAll(AppApiLive, CustomGitRoutes).pipe(
154 Layer.provide(Authentication.layer),
155);
156```
157
158Every other Git handler remains supplied by `GroupsLive`. No Git-specific
159server factory or application API argument is involved.
160
161## Serve selected groups or prefixes
162
163For a subset or modified endpoint schema, use `HttpApiBuilder.group` against
164the schema you are serving:
165
166```typescript
167class ManagementApi extends HttpApi.make("management")
168 .add(Git.Repos).prefix("/git") {}
169
170const ManagementRoutes = HttpApiBuilder.layer(ManagementApi).pipe(
171 Layer.provide(HttpApiBuilder.group(ManagementApi, "repos", (h) =>
172 Effect.map(Git.Handlers, (git) => h.handleAll(git.repos)),
173 )),
174);
175```
176
177A group built against a schema uses that schema's paths and API middleware.
178Changing a different API schema later does not change those registered routes.
179
180## Internal hashing
181
182`Git.InternalApiLive` registers the internal hashing endpoint separately.
183The self-binding hasher calls it with a deploy-time internal secret. Mount it
184outside user authentication, as in the server assembly above. `Git.ApiLive`
185contains only the public Git surface and does not mount internal routes.