Your First Worker
In this guide you will build a Stratal worker with a single GET /api/hello endpoint that returns a JSON greeting. By the end you will have a running worker with a controller, a root module, and an entry point.
Create a controller
Section titled “Create a controller”Controllers handle incoming requests. Create src/hello.controller.ts:
import { Controller, IController, Route, RouterContext } from 'stratal/router'import { z } from 'stratal/validation'
@Controller('/api/hello')export class HelloController implements IController { @Route({ response: z.object({ message: z.string() }), }) index(ctx: RouterContext) { return ctx.json({ message: 'Hello World' }) }}A few things to note:
-
@Controller('/api/hello')registers this class as a controller and sets its base path. -
IControlleris the interface every controller implements. It defines convention-based method names that map to HTTP verbs automatically:Method HTTP verb Route index()GET /api/helloshow()GET /api/hello/:idcreate()POST /api/helloupdate()PUT /api/hello/:idpatch()PATCH /api/hello/:iddestroy()DELETE /api/hello/:idYou only implement the methods you need — here we define
index()to handleGET /api/hello. -
@Routeconfigures the route. Theresponseproperty is a Zod schema that validates the response body and feeds into automatic OpenAPI documentation. -
RouterContextgives you access to the request, params, and helper methods likectx.json().
Create the root module
Section titled “Create the root module”Every Stratal application has a root module that declares which controllers (and later, providers) belong to the app. Create src/app.module.ts:
import { Module } from 'stratal/module'import { HelloController } from './hello.controller'
@Module({ controllers: [HelloController],})export class AppModule {}The @Module decorator accepts an options object with:
controllers— an array of controller classes to register.providers— services and other injectable classes (covered in Dependency Injection).imports— other modules to compose into this one (covered in Modules).
For now, a single controller is all you need.
Create the entry point
Section titled “Create the entry point”The entry point is the file Wrangler invokes when a request arrives. Create src/index.ts:
import 'reflect-metadata'import { Stratal } from 'stratal'import { AppModule } from './app.module'
export default new Stratal({ module: AppModule })import 'reflect-metadata'must appear once at the top of your entrypoint. It enables the decorator metadata that Stratal’s dependency injection (powered bytsyringe) relies on.Stratalis the framework entry point. It eagerly bootstraps the module system, router, and DI container.- The
moduleoption points to your root module.
Run the dev server
Section titled “Run the dev server”Start the local development server:
npx wrangler devOnce Wrangler is ready you will see output like:
⎔ Starting local server...Ready on http://localhost:8787Test your endpoint:
curl http://localhost:8787/api/helloYou should receive:
{ "message": "Hello World" }What just happened
Section titled “What just happened”Here is the path a request takes through your worker:
- Wrangler receives the HTTP request and hands it to the exported
Stratalinstance. - Stratal passes the request to the router, which runs inside an initialized DI container.
- The router matches
GET /api/hellotoHelloController.index()using the convention-based mapping. - The controller method runs and returns a JSON response.
- The response is sent back to the caller.
Add a service
Section titled “Add a service”Real applications rarely put business logic directly in controllers. Stratal uses dependency injection to keep concerns separated. Let’s extract the greeting into a service.
Create src/hello.service.ts:
import { Transient } from 'stratal/di'
@Transient()export class HelloService { greet(name: string): string { return `Hello, ${name}!` }}@Transient() marks the class as injectable. By default it creates a new instance each time it is resolved.
Now update the controller to use the service. Replace the contents of src/hello.controller.ts:
import { Controller, IController, Route, RouterContext } from 'stratal/router'import { z } from 'stratal/validation'import { HelloService } from './hello.service'
@Controller('/api/hello')export class HelloController implements IController { constructor(private readonly helloService: HelloService) {}
@Route({ response: z.object({ message: z.string() }), }) index(ctx: RouterContext) { const message = this.helloService.greet('World') return ctx.json({ message }) }}Register the service as a provider in src/app.module.ts:
import { Module } from 'stratal/module'import { HelloController } from './hello.controller'import { HelloService } from './hello.service'
@Module({ controllers: [HelloController], providers: [HelloService],})export class AppModule {}Restart the dev server and hit the endpoint again — the response is now {"message":"Hello, World!"}, produced by the injected service.
Next steps
Section titled “Next steps”Your project should now look like this:
my-worker/├── src/│ ├── app.module.ts│ ├── hello.controller.ts│ ├── hello.service.ts│ └── index.ts├── package.json├── tsconfig.json└── wrangler.jsoncFrom here you can explore:
- Project Structure — recommended directory layout as your app grows.
- Controllers and Routing — custom routes, path params, and the
handle()escape hatch. - Dependency Injection — scopes, tokens, and advanced DI patterns.
- Modules — composing feature modules and sharing providers.