Run it locally under Aspire
You have built the whole SAP→Dynamics sync: a file-watch import job, a sandboxed transform task, a
queue, and a cron schedule. This final chapter runs all of it together — workers,
triggers, queue, and cron processors — on one machine under aspire start, and shows you how to read
the running system from the dashboard. It is the local orchestration story: one command,
one observable stack, throwaway infrastructure.
And you wrote none of the wiring that makes that possible. netscript init scaffolded the AppHost
back in Chapter 1; every plugin you added since contributed its own API and background processor to
the graph on its own; a single aspire start then boots the lot with cross-references resolved for
you. That is the scaffold-to-Aspire story this chapter surfaces end to end — from an empty folder to
a live, correctly-wired, observable stack with no hand-written orchestration config in between.
What you will build
By the end of this chapter you will bring the full my-erp/ resource graph up with a single
aspire start — Postgres, Redis, the workers API + processor, and the triggers API + processor (which
runs your file-watch and cron triggers) — initialize the database through the running AppHost, and
read the live import pipeline in the Aspire dashboard: resources, console logs, and the traces that
stitch a file drop to its job execution.
Before you begin
You need the complete my-erp/ workspace from Chapter 4:
the workers and triggers plugins, the import-products job, the normalize-sap transform task,
the product-import-trigger file watch, and the daily-resync-schedule cron. Docker must be
running so Aspire can provision Postgres and
Redis. Confirm the AppHost was scaffolded (it is a TypeScript/Node program, not C#):
ls aspire/apphost.mts aspire/aspire.config.json
Expected: both files exist. netscript init generated them in Chapter 1; you never hand-write them.
Step 1 — Understand the graph you are about to run
The resource graph is derived from your installed plugins at boot via composeAppHost — add a
plugin and its API plus background processor appear; remove it and they vanish, no edit to
apphost.mts. With workers and triggers installed, a single aspire start stands up this graph:
| Name | Type | Description |
|---|---|---|
aspire (dashboard) |
https://localhost:18888 / http://localhost:18889 |
Live resource list, console logs, structured logs and traces. A login token is printed on start. |
OTLP collector |
http://localhost:4318 |
OpenTelemetry endpoint the dashboard runs; framework spans and structured logs land here automatically. |
postgres |
Container |
Throwaway Docker Postgres. The database netscript db commands target — reachable only while Aspire is up. |
redis |
Container (cache) |
Redis cache — the default --cache-backend; Redis-compatible. Backs the KV/queue workloads — this is what auto-discovery resolves the queue to once it is up. |
workers API + processor |
allocated port + executable |
The API enqueues; a separate background processor drains the queue and runs your import job. |
triggers API + processor |
allocated port + executable |
The API plus the processor that runs your file-watch and cron triggers. |
Step 2 — Restore and run
The AppHost runs on its own isolated Node runtime inside aspire/ so its dependencies never leak
into your Deno workspace. Restore once per machine, then run — both from inside aspire/:
cd aspire
aspire restore # one-time SDK restore (and after an SDK bump)
aspire start # boots the whole graph; prints the dashboard URL + a login token
aspire start brings up infrastructure first, then the plugin APIs and background processors, with
cross-references resolved into injected environment variables. Leave it running.
Step 3 — Initialize the database through the running AppHost
With Aspire up, Postgres is live and the netscript db commands can reach it. Run them from the
workspace root in a second terminal (leave aspire start going in the first):
netscript db init --name init # create + apply the first migration
netscript db generate # generate the Prisma client
netscript db seed # optional: seed development data
These talk to the Postgres container Aspire provisioned. Run them with no Aspire up and they fail — there is no Postgres for them to reach.
Step 4 — Watch the pipeline run end to end
With the full stack up, exercise the pipeline you built and read it from the dashboard. Drop a file in the SAP export shape:
cat > .data/incoming/products_live.csv <<'CSV'
material_no,description,price_cents
ANV-9,Anvil,4999
CSV
mv .data/incoming/products_live.csv .data/incoming/products/products_live.csv
Then open https://localhost:18888, paste the login token aspire start printed, and use the three
dashboard surfaces:
| Name | Type | Description |
|---|---|---|
Resources |
tab |
Every container and executable with status and the port it bound. Confirm workers, triggers, postgres, and redis are all green. |
Console logs |
tab |
stdout/stderr per resource. Open the workers processor to read your import job's log lines; the triggers processor shows the file-watch event. |
Structured logs + Traces |
tab |
Spans correlated by traceparent. The framework instruments job dispatch and execution automatically, so a file drop → job run trace appears with no extra wiring. |
Because Aspire starts each resource with an OTLP endpoint pointed at http://localhost:4318,
framework-level spans (job dispatch, job execution, scheduler runs) surface in the Traces view on
their own.
Verify your progress
Confirm the whole graph is healthy and the pipeline ran:
# Both plugin APIs answer (endpoints from the Resources tab).
curl <workers-endpoint>/health
curl <triggers-endpoint>/health
# The dropped file produced an import execution.
ns-workers executions --limit=10 --json
Expected: both health checks return healthy JSON, and the executions feed shows a completed
import-products run for products_live.csv.
- [ ]
aspire startis up; the dashboard listspostgres,redis, workers, and triggers all green. - [ ]
netscript db init/generatesucceeded against the Aspire Postgres. - [ ]
curl <workers-endpoint>/healthandcurl <triggers-endpoint>/healthboth return healthy. - [ ] A file drop produced an
import-productsexecution and a dispatch/execution trace in the dashboard.
What you built
You ran the complete SAP→Dynamics sync — workers, triggers, queue, and cron — on one machine under a
single aspire start, initialized the database through the running AppHost, and watched a SAP
export flow into a durable job execution with its trace in the dashboard. You now have an
end-to-end durable background-processing backend that could carry a real migration's parallel-run,
and you know exactly where the local story ends and a production deployment begins.
Where to go next
You have finished the ERP Sync track. From here, branch into task-oriented and reference docs:
- Ship it remotely → Deploy — the production companion to this local run:
deployable units, managed backing services, and the
--no-aspirepath. - Take the transform polyglot → Run a polyglot task — swap Chapter 3's Deno transform for a Python or shell step on your own host.
- Tune throughput → Choose a queue provider and Tune the worker runtime.
- Understand the orchestrator → Orchestration with Aspire and the full How-to guides.