Your First Worker
Understand the three files that make a Base worker, then build a typed, validated JSON API.
By the end of this tutorial you'll understand every moving part of the worker you scaffolded, and you'll have built your own service: a small notes API with typed, validated JSON input.
You'll need the my-app workspace from Installation, with the dev server running (npx base develop -w app).
The three files that matter
index.ts: The entry point
That's the whole file, and it rarely changes. BaseWorker.create(Settings) exports the standard Cloudflare Worker shape (fetch / queue / scheduled). The startup/preload import must come first; it loads the runtime support decorators depend on.
Notice the import paths: Base has no barrel files. Every import is a per-file subpath like @system-inc/base-foundation/worker/BaseWorker. Your editor's auto-import handles this, and it keeps bundles lean and dependencies explicit.
settings.ts: What your worker is
Two things to internalize:
servicesis one flat list for every kind of class. There is no separate list of routes, resolvers, or processors. Each class's decorator declares its role (@HttpServicemeans HTTP routes,@GqlResolvermeans GraphQL) and Base sorts them at boot. A listed class with no recognized decorator is a boot error: registration is explicit, never magic.'@default'is an environment key. Settings likeserverare maps keyed by environment name (Development,Production, …) with'@default'as the fallback. One settings file describes every environment.
The service: Where your code lives
source/services/HelloWorldService.ts:
@HttpService()marks the class as an HTTP dispatch surface;@HttpRoute(method, path)binds each method to a route.@HttpPath('name')pulls the:namesegment out of the URL and hands it to you as an argument. No request parsing in your handler.@Injectable()+@Inject(GreetingService)is dependency injection:GreetingServiceis resolved from the container and handed to the constructor.
And the service being injected (source/services/GreetingService.ts) is just a plain class:
No HTTP anywhere in it. Keeping the real logic in plain services makes it reusable by any handler that injects it, and unit-testable in isolation; the scaffold ships GreetingService.test.ts to prove the point.
Each incoming request gets a fresh request-scoped container, your service is resolved from it, the route method runs, and the response goes back. That's the whole lifecycle from your code's point of view.
Build a notes API
Time to write your own service. You'll build POST /notes, GET /notes, and GET /notes/:id, with a typed, validated request body.
Create workers/app/source/services/NoteService.ts:
What's new here:
@HttpBody(() => CreateNoteInput)deserializes the JSON body into a realCreateNoteInputinstance and validates it before your handler runs. The@SerializableObject()/@SerializableFielddecorators define the shape;@VerifyIsNotEmpty()adds a validation rule. Invalid input never reaches your code.- Handlers can return plain objects. Return a
Responsewhen you need full control; return an object and Base wraps it inResponse.json(...)for you. @HttpPath('id', () => Number)coerces the path segment to a number before you see it.- Thrown
HttpErrorsbecome proper HTTP responses:HttpErrors.notFound(...)is a 404 with a structured error body.
Register it
Every class must be listed to exist. Add NoteService to settings.ts:
Order matters here: routes bind in registration order, and HelloWorldService's
parameterized GET /:name matches /notes too. Listing NoteService first
means its routes are tried first; otherwise GET /notes would answer
Hello, notes!.
Try it
The dev server reloads on save. Create a note:
List and fetch:
Now try to break it by sending an empty title:
Validation rejects it before your handler runs. That's the validation pipeline you'll meet again, unchanged, in GraphQL and RPC.
One problem
Restart the dev server and fetch /notes again: empty. Workers are stateless: in-memory data lives only as long as the runtime instance. Real data needs a real database.
Next: Add a Database, where you define an entity, run migrations, and persist your notes in Cloudflare D1.