Tech Stack
The key technologies you encounter when building on Neutrino — IDs, validation, state management, and more.
Tech Stack
This page covers the stack pieces you encounter as a developer building on or with Neutrino. Internal infrastructure choices (Drizzle ORM, TanStack Router, Module Federation) are mentioned briefly with links to the full internal reference.
NanoID — Platform and resource identifiers
Every platform, stack, tenant, and resource in Neutrino has a 10-character NanoID as its canonical identifier. The alphabet is [a-z0-9] (lowercase letters and digits only — no uppercase, no hyphens, no special characters).
// Example IDs you will see everywhere
const platformId = "k3m9p2xw7q"; // platform
const stackId = "x7y8z9w0q1"; // stack
const tenantId = "m3n4p5q6r7"; // tenantIDs appear in:
- DNS hostnames:
auth.svc.default.k3m9p2xw7q.nno.app - Cloudflare resource names:
k3m9p2xw7q-default-auth-db - API paths:
/api/nno/platforms/k3m9p2xw7q/...
Always use generateId() from @neutrino-io/core/naming — never generate IDs by hand.
import { generateId, buildResourceName } from "@neutrino-io/core/naming";
const platformId = generateId();
// → 'k3m9p2xw7q'
buildResourceName(platformId, "default", "auth");
// → 'k3m9p2xw7q-default-auth'NanoID was chosen over UUID v4 because 10-character IDs are compact enough to embed in DNS labels, URL paths, and Cloudflare resource names without exceeding length limits — while still providing sufficient collision resistance for the expected platform count.
Zod — Request and response validation
Neutrino uses Zod for all request and response validation across backend services. Zod schemas are the single source of truth: they define the TypeScript types and enforce runtime correctness in the same declaration.
Where you encounter Zod
When you call Neutrino APIs, the Gateway validates your request body against a Zod schema before the handler runs. Invalid requests receive a structured error response:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"requestId": "req_01j..."
}
}In feature SDK hooks
Feature hooks that accept user-supplied data validate inputs with Zod before passing them to the API:
import { z } from "zod";
const CreateTenantSchema = z.object({
name: z.string().min(2).max(64),
slug: z
.string()
.regex(/^[a-z0-9-]+$/)
.max(32),
});
// In your feature — this is validated before the API call is made
const result = await createTenant(CreateTenantSchema.parse(formData));Error codes
The NnoErrorEnvelopeSchema and ErrorCode union are exported from @neutrino-io/core/errors and used across all services to ensure consistent error shapes.
TanStack Query — Server state in console and features
The Neutrino console shell and all feature packages use TanStack Query (@tanstack/react-query) for server state management — fetching, caching, and synchronising data from backend APIs.
Why TanStack Query
- Features bring their own query keys and cache logic without central coordination
- Stale-while-revalidate behaviour works out of the box
- Natural pairing with the feature SDK: each feature manages its own data without knowing about other features
Typical pattern in a feature hook
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useShell } from "@neutrino-io/sdk/feature";
export function useTenants() {
const { platform } = useShell();
return useQuery({
queryKey: ["tenants", platform.id],
queryFn: () => fetchTenants(platform.id),
staleTime: 30_000,
});
}
export function useCreateTenant() {
const queryClient = useQueryClient();
const { platform } = useShell();
return useMutation({
mutationFn: (data: CreateTenantInput) => createTenant(platform.id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["tenants", platform.id] });
},
});
}Query key convention: prefix your query keys with your feature ID (e.g.
['analytics', ...]) to avoid collisions with other features sharing the same shell query cache.
Internal stack — brief reference
These choices are internal to how Neutrino is built. You interact with their effects (typed routes, generated API clients, module-level isolation) but do not configure them directly.
| Technology | Role | Where it lives |
|---|---|---|
| Drizzle ORM | Type-safe DB queries | services/iam, services/registry only |
| TanStack Router | Type-safe routing in the console shell | apps/console |
| Module Federation (Phase 2) | Hot-swappable remote feature loading | Not yet implemented |
| Hono.js | Backend service framework on CF Workers | All services/* |
| Better Auth | Per-platform auth Workers | services/auth template |
| Vite | Console shell build tooling + feature auto-discovery plugin | apps/console |
For the full rationale behind each choice see ADR-009.