Quickstart
In ten minutes you will create a Norn project, run an order workflow on a local Temporal server, send it a signal, and stop the whole thing halfway through to watch it pick up where it left off.
You need Node.js 22.18 or later. Nothing else: the project downloads and starts a local Temporal server for you on the first run. If you already run one (temporal server start-dev), it uses that instead.
-
Create the project.
Terminal window pnpm create @norn-ts my-appTerminal window npm create @norn-ts@latest my-appTerminal window yarn create @norn-ts my-appThis copies a small project into
my-app/and installs its dependencies. The rest of this page uses pnpm; with npm, writenpm run devforpnpm dev, and so on. -
Start it.
Terminal window cd my-apppnpm devStarting a local Temporal server (the first run downloads it)...Temporal is running on localhost:7233. Web UI: http://localhost:8233(webpack's report on bundling the workflows)Worker running. Press Ctrl+C to stop.Two things are now running: a Temporal server, which stores every workflow’s history, and your worker, the process that runs your workflow and activity code. The worker restarts when you save a file.
-
Start an order. In a second terminal, in
my-app:Terminal window pnpm orderStarted order-o-mh3k2x1a: http://localhost:8233/namespaces/default/workflows/order-o-mh3k2x1aCancel it within 5 minutes with: pnpm order cancel o-mh3k2x1aOpen the link. The Temporal Web UI shows the workflow’s event history: the workflow started, the
payments.chargeactivity was scheduled and completed (your first terminal logscharged 1999 cents), and a five-minute timer started. The workflow is now waiting, and it isn’t using any memory or CPU on the worker while it waits. -
Send it a signal. Use the order ID from the previous step:
Terminal window pnpm order status o-mh3k2x1apnpm order cancel o-mh3k2x1apnpm order status o-mh3k2x1apaidCanceled o-mh3k2x1a.refundedstatusis a query: it reads the workflow’s state without changing it.cancelis a signal: it delivers a message the workflow reacts to. The workflow stops waiting, runs thepayments.refundactivity and completes. Refresh the Web UI to see both in the history. -
Pull the plug. Start another order with
pnpm order, then press Ctrl+C in the first terminal to stop everything: the worker and the Temporal server. Start it again withpnpm dev, and ask the order for its status:Terminal window pnpm order status <id>It is still
paid, and still waiting out its five minutes. Temporal stored the history in.temporal/dev.db. When the worker came back, it replayed the history to rebuild the workflow’s state and carried on. In production this is what happens when a deploy, a crash or a lost machine takes a worker away. Durable execution in 5 minutes explains how replay works. -
Change the code. Open
src/services/payment-gateway.tsand change theconsole.logincharge(). Save, and the worker restarts. Runpnpm orderagain to see your message. -
Run the tests.
Terminal window pnpm test✓ test/order.workflow.test.ts (3 tests)Tests 3 passed (3)The tests run on Temporal’s time-skipping test server, so the five-minute wait takes no time at all. The activities are fakes (plain
vi.fn()s), so the tests never touch the payment gateway.
What’s in the project
Section titled “What’s in the project”Directorysrc/
Directoryworkflows/
- order.workflow.ts the workflow
- index.ts exports every workflow the worker runs
Directoryactivities/
- payment.activities.ts the steps that do I/O
Directoryservices/
- payment-gateway.ts what the activities call
- worker.ts the worker process
- order.ts the client script behind
pnpm order
Directorytest/
- order.workflow.test.ts
Directoryscripts/
- dev.ts what
pnpm devruns
- dev.ts what
- .claude/skills/norn/SKILL.md teaches coding agents about Norn
- AGENTS.md
- package.json
Here’s how the pieces fit together:
- The workflow (
src/workflows/order.workflow.ts) is a class decorated with@Workflow. Itsrun()method is the business process: charge, wait up to five minutes for a cancellation, maybe refund. Its@Signaland@Querymethods are how the outside world talks to it. Workflow code must be deterministic, because Temporal replays it, so it never does I/O itself. Determinism & the sandbox explains the rules. - The activities (
src/activities/payment.activities.ts) do the I/O. They’re a plain class. The workflow imports it withimport typeand calls it throughuseActivities<PaymentActivities>('payments', { timeout, retries }), which is also where the retry policy lives. Activity code never reaches the workflow sandbox. - The worker (
src/worker.ts) hands both tocreateWorker(): the module that exports your workflows, and your activity objects by name ({ payments: new PaymentActivities(gateway) }). That name is how the workflow finds them. - The client (
src/order.ts) usescreateClient()and the workflow class to start, signal and query orders, with every name and argument type-checked.
Next steps
Section titled “Next steps”- New to Temporal? Read Durable execution in 5 minutes.
- Already know Temporal? Coming from the Temporal SDK maps the SDK to Norn.
- Writing your own? Workflows and Activities cover everything the starter doesn’t.
- Adding Norn to an existing project?
pnpm add @norn-ts/norn @temporalio/client @temporalio/common @temporalio/worker @temporalio/workflow, then copysrc/worker.ts,vitest.config.tsandtsconfig.jsonfrom the starter. Norn needs standard decorators (noexperimentalDecorators).