Skip to content

Part 5: Add your application's API

Your Git host already identifies callers. Add /api/v1/me to return the current user. This part introduces an application API as a sibling of the existing Git routes; it uses the same Worker, URL, and authentication.

Create src/api.ts:

src/api.ts
import * as Schema from "effect/Schema";
const User = Schema.Struct({ id: Schema.String });
class Unauthorized extends Schema.TaggedError<Unauthorized>()(
"Unauthorized",
{},
{ httpApiStatus: 401 },
) {}

The endpoint returns the caller’s ID. Its error schema declares that a missing caller is an HTTP 401.

Add the API imports:

src/api.ts
import * as Schema from "effect/Schema";
import * as HttpApi from "effect/unstable/httpapi/HttpApi";
import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint";
import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";

Append the endpoint and its group:

// src/api.ts (append)
const Me = HttpApiEndpoint.get("me", "/api/v1/me", {
success: User,
error: Unauthorized,
});
class AppRoutes extends HttpApiGroup.make("app").add(Me) {}
export class AppApi extends HttpApi.make("app").add(AppRoutes) {}

AppRoutes is your application’s group. AppApi describes that group, including the path and response schema of /api/v1/me.

Add these imports:

src/api.ts
import * as Schema from "effect/Schema";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";
import { Session } from "./session.ts";

Append the implementation:

// src/api.ts (append)
const MeLive = HttpApiBuilder.group(AppApi, "app", (h) =>
h.handle("me", () =>
Effect.gen(function* () {
const { user } = yield* Session;
if (user === null) return yield* new Unauthorized();
return user;
}),
),
);
export const AppApiLive = HttpApiBuilder.layer(AppApi).pipe(
Layer.provide(MeLive),
);

The handler reads Session, which the middleware from Part 4 supplies. MeLive implements your group; AppApiLive registers its HTTP routes.

Import the new route layer in src/git.ts:

src/git.ts
import { Authentication } from "./middleware.ts";
import { AppApiLive } from "./api.ts";

Merge it beside Git before applying the middleware:

src/git.ts
const PublicRoutes = Git.ApiLive.pipe(
const PublicRoutes = Layer.mergeAll(AppApiLive, Git.ApiLive).pipe(
Layer.provide(Authentication.layer),
);

Both sets of routes now run with the same access policy and request session. Your application’s API definition and handlers stay in src/api.ts.

Terminal window
bun alchemy deploy

No new storage resource is needed for this change.

Use the session cookie from Part 4:

Terminal window
curl --fail-with-body -b dana.cookies "$HOST/api/v1/me"

Expect {"id":"..."} with the same ID as $OWNER. The API key also identifies Dana:

Terminal window
curl --fail-with-body -u "x:$KEY" "$HOST/api/v1/me"

Without either credential, /me returns 401:

Terminal window
curl -i "$HOST/api/v1/me"

Verify that Git still works on the same host:

Terminal window
git -c credential.helper= -C work fetch origin

Enter Dana’s API key when prompted. Continue to Part 6: Protect a branch.