New to Runner? This guide gets you to a working example as quickly as possible.
What you’ll build
You’ll create a simple task that greets users, demonstrating:
Task definition with dependencies
Resource lifecycle (init/dispose)
Middleware (automatic retries)
Input validation with Zod
Running your app
Installation
Install dependencies
Install Runner and Zod for schema validation: npm install @bluelibs/runner zod
npm install -D typescript tsx
This guide assumes Node.js 18+ and TypeScript 5.6+. See Installation for detailed requirements.
Create your first task
Create a file called index.ts and add the following code: import { r , run , globals } from "@bluelibs/runner" ;
import { z } from "zod" ;
// Resources are singletons with lifecycle management and async construction
const db = r
. resource ( "app.db" )
. init ( async () => {
const conn = await postgres . connect ( process . env . DB_URL );
return conn ;
})
. build ();
const mailer = r
. resource ( "app.mailer" )
. init ( async () => ({
sendWelcome : async ( email : string ) => {
console . log ( `Sending welcome email to ${ email } ` );
},
}))
. build ();
// Define a task with dependencies, middleware, and zod validation
const createUser = r
. task ( "users.create" )
. dependencies ({ db , mailer })
. middleware ([ globals . middleware . task . retry . with ({ retries: 3 })])
. inputSchema ( z . object ({ name: z . string (), email: z . string (). email () }))
. run ( async ( input , { db , mailer }) => {
const user = await db . users . insert ( input );
await mailer . sendWelcome ( user . email );
return user ;
})
. build ();
// Compose resources and run your application
const app = r
. resource ( "app" ) // top-level app resource
. register ([ db , mailer , createUser ]) // register all elements
. build ();
const runtime = await run ( app );
await runtime . runTask ( createUser , { name: "Ada" , email: "[email protected] " });
// await runtime.dispose() when you are done.
Run your app
Execute your TypeScript file: You should see output like: That’s it! You now have a working Runner application.
What just happened?
Let’s break down what you built:
Resources (singletons with lifecycle)
const db = r
. resource ( "app.db" )
. init ( async () => {
const conn = await postgres . connect ( process . env . DB_URL );
return conn ;
})
. build ();
Resources are singletons with init and dispose lifecycle methods. They’re perfect for databases, HTTP servers, or anything that needs setup and teardown.
Tasks (business logic with DI)
const createUser = r
. task ( "users.create" )
. dependencies ({ db , mailer })
. middleware ([ globals . middleware . task . retry . with ({ retries: 3 })])
. inputSchema ( z . object ({ name: z . string (), email: z . string (). email () }))
. run ( async ( input , { db , mailer }) => {
const user = await db . users . insert ( input );
await mailer . sendWelcome ( user . email );
return user ;
})
. build ();
Tasks are async functions with:
Explicit dependencies : dependencies({ db, mailer }) injects what the task needs
Middleware : Add cross-cutting concerns like retries, timeouts, or caching
Validation : Input and output schemas ensure type safety at runtime
Type safety : Full TypeScript inference throughout
App composition
const app = r
. resource ( "app" )
. register ([ db , mailer , createUser ])
. build ();
The root resource composes everything together. Runner wires all dependencies automatically.
Runtime
const runtime = await run ( app );
await runtime . runTask ( createUser , { name: "Ada" , email: "[email protected] " });
The run() function initializes all resources, wires dependencies, and returns a runtime object with methods like runTask, emitEvent, and dispose.
Next steps
Tasks Learn more about defining and composing tasks
Resources Understand resource lifecycle and patterns
Middleware Add reliability with built-in middleware
Testing Write tests for your Runner apps