Quickstart
This guide walks you through building a minimal Digital Alchemy application from scratch. By the end you'll have a running app and understand the three core building blocks: services, modules, and bootstrap.
Step 1 — Write a service​
A service is a plain TypeScript function that receives one parameter: TServiceParams. This object contains everything your service needs — the logger, lifecycle hooks, config, references to other services, and more.
import type { TServiceParams } from "@digital-alchemy/core";
export function HelloService({ logger, lifecycle }: TServiceParams) {
lifecycle.onReady(() => {
logger.info("Hello, Digital Alchemy!");
});
}
Three things to notice:
- The function always receives a single destructured
TServiceParams. That's the contract. - No code runs at function call time. You register callbacks for lifecycle stages. Here,
onReadyfires after everything is initialized. This is how you control when things happen. - The logger is pre-bound to your service's context (
my_app:hello), so every log line is automatically tagged with the module and service name.
Step 2 — Create an application module​
An application module wires services together and gives the app a name.
import { CreateApplication } from "@digital-alchemy/core";
import { HelloService } from "./hello.service.mts";
export const MY_APP = CreateApplication({
name: "my_app",
services: {
hello: HelloService,
},
});
// Extend LoadedModules so TypeScript knows what's on TServiceParams
declare module "@digital-alchemy/core" {
export interface LoadedModules {
my_app: typeof MY_APP;
}
}
The declare module block is how Digital Alchemy's type system works. After this declaration, any service in my_app can access my_app.hello from TServiceParams and TypeScript knows its exact type — inferred from HelloService's return value.
Step 3 — Bootstrap​
Create an entrypoint that starts the application.
import { MY_APP } from "./application.mts";
await MY_APP.bootstrap();
Then run it:
npx tsx src/main.mts
Expected output:
[Mon 09:00:00.000] [INFO][my_app:hello]: Hello, Digital Alchemy!
Try it live​
The editor below contains the same three files. Click between them, read the types, and try making changes.
What just happened?​
When bootstrap() is called:
- Boilerplate services are wired first — logger, config, scheduler, and async local storage
HelloServiceis called once — receiving aTServiceParamsbuilt from everything wired so far- Lifecycle stages run in order:
PreInit→PostConfig→Bootstrap→Ready - Your
onReadycallback fires at step 3 — all services are wired, config is validated
The service function itself runs during wiring (step 2). The onReady callback runs later (step 3). This separation is why you can safely access other services and config values inside lifecycle callbacks without worrying about initialization order.
For the full picture of what happens during bootstrap, see Bootstrap Internals.
Next steps​
- Next Steps →
- Add a second service → Adding Services
- Add typed configuration → Typed Configuration
- Understand the lifecycle → Lifecycle Overview