# FastAng
> FastAng combines Angular pages and Fastify APIs in a TypeScript full-stack framework with file-based routing, server-side rendering, explicit prerendering and a Vercel adapter.
Current release: 0.1.0-alpha.6 (experimental alpha). These guides describe the current release; before v1, older alpha documentation is not hosted. Check the installed package version before applying examples.
Use Angular components, guards, resolvers and HttpClient for the frontend. Use native Fastify request/reply objects and plugins for the backend. Keep server code and secrets out of browser imports. FastAng does not use React Server Components or Next.js APIs.
Create an app with `npm create fastang@alpha my-app -- --install` or `pnpm create fastang@alpha my-app --install`. Run `npm run dev` or `pnpm run dev` inside the app; the default port is 3278. The npm release channel is `alpha`; do not assume a stable `latest` release exists. Read the guides for runtime requirements, public APIs and supported deployment modes.
---
Source: https://fastang.dev/en/docs/getting-started.md
# Get started with FastAng
Create a project, start the server and try an Angular page that reads a Fastify API.
## Requirements
This guide targets FastAng `0.1.0-alpha.6`. Use Node `^22.22.3`, `^24.15.0` or `>=26.0.0`, with npm or pnpm 10/11. The starter uses Angular `22.2.3`, Vite `8.3.4` and Fastify `^5.12.7`. APIs may change between alpha releases.
## Create and run
```bash
npm create fastang@alpha my-app -- --install
cd my-app
npm run dev
```
Open `http://localhost:3278`. Port 3278 spells FAST on a telephone keypad. The generator creates a new directory and rejects existing destinations. `--install` runs npm install. Without it, run npm install in the project before starting the server.
## pnpm support
With pnpm 10 or 11, you can create the project and install its dependencies in one command:
```sh
pnpm create fastang@alpha my-app --install
cd my-app
pnpm run dev
```
Open `http://localhost:3278`. FastAng recognizes pnpm and uses it to install
dependencies and set up the project's scripts. Installation creates
`pnpm-lock.yaml`: commit it alongside `package.json` to keep dependency versions
consistent across computers.
To install dependencies later, replace `--install` with `--no-install`, then run
`pnpm install` in the project directory.
When running the generator through another package manager, add
`--package-manager pnpm` to select pnpm explicitly.
### Project commands
Run these commands from the application directory:
| Command | Purpose |
| --- | --- |
| `pnpm run dev` | Start the development server with page updates. |
| `pnpm run check` | Check routes, TypeScript types and Angular templates. |
| `pnpm run build` | Check the project and create a production build. |
| `pnpm start` | Start the server from the production build. |
| `pnpm run build:vercel` | Prepare the build for deployment on Vercel. |
To update an existing application, follow [the pnpm upgrade steps](https://fastang.dev/en/docs/upgrading.md#pnpm-updates).
### Blocked installation scripts
If pnpm reports blocked scripts, for example with `Ignored build scripts`, run:
```sh
pnpm approve-builds
pnpm install
```
In the first command, select the dependencies whose scripts you want to allow.
Then repeat the installation. Project permissions are stored in
`pnpm-workspace.yaml`.
## Standalone CLI
To use `fastang new` directly, install the lightweight CLI globally:
```sh
npm install -g @fastang/cli@alpha
fastang new another-app --install
```
To create a pnpm project with the global CLI, use
`fastang new another-app --package-manager pnpm --install`.
Without a global installation, run
`npx --package=@fastang/cli@alpha fastang-cli new another-app --install`.
The CLI uses the matching generator release. Development and build use the core
installed in the app. See [upgrading](https://fastang.dev/en/docs/upgrading.md) for existing projects.
## Try the project
| URL | What to check |
| --- | --- |
| `/` | The counter responds after Angular hydration. |
| `/greet/world` | The resolver reads a greeting from Fastify. |
| `/api/health` | JSON response with `ok: true`. |
| `/about` | A prerendered page in the production build. |
| `/missing` | A not-found page with HTTP 404 on the server. |
In development, `/about` also renders on the server for each request. Prerendering applies to production builds.
## Where to work
- `src/app/pages`: pages, layouts and router metadata.
- `src/server/api`: Fastify endpoints with the method in the filename.
- `src/server/plugins.ts`: server plugins and hooks.
- `fastang.config.ts`: prerender URLs and Vercel options.
## Use FastAng with an AI assistant
Give your assistant the [llms.txt](https://fastang.dev/llms.txt) URL. It contains
a FastAng overview and links to Markdown guides without the website navigation.
You can start with this request:
Read https://fastang.dev/llms.txt and the relevant guides. Before proposing
changes, check the @fastang/core version in the project's package.json.
Use the documented FastAng, Angular and Fastify APIs and keep server code
separate from browser code.
If your tool accepts attached documents but cannot follow links, download
[llms-full.txt](https://fastang.dev/llms-full.txt) and add it to the context:
it contains all current guides in English. You can also supply just the
individual Markdown guides listed in the index to keep the context focused
on the task.
Not every assistant reads these files automatically: explicitly provide the
URL or attach the document. Before v1, the guides track the current alpha;
for projects on older versions, also read the
[release notes](https://github.com/daclab-dev/fastang/releases).
Always verify generated changes with `npm run check` and `npm run build`,
or the equivalent pnpm commands, and your application's tests.
## Check production
```bash
npm run check
npm run build
npm start
```
Stop the development server before starting production on the same port. `npm run check` validates routes, types and templates without starting a server.
Continue with [pages and layouts](https://fastang.dev/en/docs/routing.md), then [APIs](https://fastang.dev/en/docs/api.md).
---
Source: https://fastang.dev/en/docs/routing.md
# Pages, routing and layouts
Define the structure with files and use Angular routing for metadata, guards and resolvers.
## File conventions
Page components export a class as default. The starter configures `provideRouter` with `withComponentInputBinding()`, so route parameters and resolved data can reach component inputs.
| File in src/app/pages | URL |
| --- | --- |
| `index.page.ts` | `/` |
| `about.page.ts` | `/about` |
| `greet/[name].page.ts` | `/greet/:name` |
| `(public)/help.page.ts` | `/help` |
| `[...slug].page.ts` | Terminal catch-all |
Parenthesized directories organize code without adding a URL segment. Static routes precede dynamic routes. The generator rejects equivalent paths and duplicate parameters.
## Add a layout
Create this file in the starter. The layout wraps `/greet/world` and stays active while navigating between its child pages.
```ts file=src/app/pages/greet/layout.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
imports: [RouterOutlet],
template: `
Greetings
`,
})
export default class GreetingLayout {}
```
A layout must include `RouterOutlet` to display its child page. You can nest more `layout.ts` files; `layout.route.ts` configures layout metadata.
## Page metadata
The adjacent `greet/[name].route.ts` exports Angular metadata. The path, component and hierarchy are generated: do not define them in metadata. Use `title`, `resolve`, `providers`, `canActivate` and `runGuardsAndResolvers` to configure route behavior.
Use `RouterLink` for internal navigation. Do not edit `src/app/fastang.routes.ts`: it is generated. `npm run generate` refreshes the manifest explicitly; the dev server detects added and removed route files.
## Per-request state
Do not keep request or user data in module variables. Use a factory for mutable route providers, for example `useFactory: () => ({ count: 0 })`. FastAng creates fresh route objects for every SSR application.
Continue with [data loading and rendering](https://fastang.dev/en/docs/rendering.md). See [Angular routing](https://angular.dev/guide/routing) for standard router behavior.
## Inspect the route map
From an application using `0.1.0-alpha.3` or later, run:
```sh
npx fastang routes
npx fastang routes --json
```
The text view lists pages in matching order with their source files, ancestor
layouts and route metadata, followed by API methods. JSON returns a
`schemaVersion: 1` document with `pages` and `api` arrays; each page includes its
`layouts` in outermost-to-innermost order. File paths are relative to the app.
Inspection reads file names without executing application code, generating files
or starting a server. Ambiguous routes and orphan metadata fail with the same
errors as generation. This is the file-based map: prefixes added by your Angular
router, plugin routes and implicit HEAD routes are not shown. It does not infer
rendering or deployment rules. The command is available from `0.1.0-alpha.3`.
---
Source: https://fastang.dev/en/docs/api.md
# Fastify APIs and validation
Register typed endpoints with runtime schemas and use native Fastify requests and replies.
## Names and methods
`src/server/api/health.get.ts` becomes `GET /api/health`. Supported suffixes are `get`, `post`, `put`, `patch`, `delete`, `head` and `options`. `[name]` creates a dynamic parameter; files without a method remain helpers and are not registered.
## Create a POST endpoint
Add this file to the starter. The TypeScript type describes the handler body; the schema validates it at runtime and defines the serialized response.
```ts file=src/server/api/messages.post.ts
import { defineApiRoute } from '@fastang/core';
export default defineApiRoute<{ Body: { text: string } }>({
schema: {
body: {
type: 'object', required: ['text'], additionalProperties: false,
properties: { text: { type: 'string', minLength: 1, maxLength: 120 } },
},
response: {
201: {
type: 'object', required: ['message'],
properties: { message: { type: 'string' } },
},
},
},
handler: async (request, reply) => reply.code(201).send({ message: request.body.text.trim() }),
});
```
## Try the response
```bash
curl -i -X POST http://localhost:3278/api/messages \
-H 'content-type: application/json' \
-d '{"text":"Hello"}'
```
The response is HTTP 201 with `{"message":"Hello"}`. A missing `text`, an empty string or a value longer than 120 characters returns HTTP 400. TypeScript types alone do not validate external requests.
## Use Fastify
The handler receives native `request` and `reply`: read parameters and headers or set the status. Register shared plugins and hooks in `src/server/plugins.ts`. The initial body limit is 1 MiB. Internal errors do not expose stacks to the client; unknown API endpoints return JSON with HTTP 404.
Do not import `src/server` modules into Angular components. Share types through a module without server dependencies and use type-only imports where possible. For shared schemas and inferred Angular requests, see [typed API contracts](https://fastang.dev/en/docs/typed-api.md).
Continue with [resolvers and SSR](https://fastang.dev/en/docs/rendering.md). Read more about [Fastify validation and serialization](https://fastify.dev/docs/latest/Reference/Validation-and-Serialization/).
## Native route options
From `0.1.0-alpha.3`, `defineApiRoute()` accepts native Fastify route options such as
`preHandler`, hook arrays, `onSend`, `bodyLimit`, `config`, `exposeHeadRoute` and
route error handlers. The following example uses these options.
```ts file=src/server/api/limited.post.ts
import { defineApiRoute } from '@fastang/core';
export default defineApiRoute<{ Body: { text: string } }>({
bodyLimit: 64,
schema: {
body: {
type: 'object', required: ['text'],
properties: { text: { type: 'string' } },
},
},
preHandler: async request => { request.body.text = request.body.text.trim(); },
onSend: async (_request, reply, payload) => {
reply.header('x-route-example', 'limited');
return payload;
},
handler: async request => ({ message: request.body.text }),
});
```
`POST /api/limited` trims the validated body and adds a response header. A JSON
body larger than 64 bytes returns HTTP 413. Hook ordering, short-circuit replies,
validation and error behavior remain Fastify's. Ordinary function hooks retain
Fastify's `this` binding; arrow functions use normal JavaScript lexical binding.
Method and URL always come from the filename and cannot be supplied as options.
`defineContractRoute()` accepts the same options, with request/reply types inferred
in hooks, but its schema always comes from the shared contract. Its error replies
must match the statuses declared there. Keep plugin-specific option types in the
plugin's Fastify type augmentation. A second generic parameter can describe a
custom route `config` object. Schema-less routes are allowed, as in native Fastify;
TypeScript types alone never validate input.
---
Source: https://fastang.dev/en/docs/typed-api.md
# Shared typed API contracts
From `0.1.0-alpha.3`, a shared TypeBox contract can infer Fastify handler types
and Angular JSON request/response types from the same runtime schemas. This is
optional: the minimal starter and `defineApiRoute()` work without TypeBox.
## Install the optional schema library
```sh
npm install @sinclair/typebox@^0.34.41
```
Start with the generated starter. Keep its greeting page and replace the following
files. Shared modules must not import server code or secrets.
## Define the contract
```ts file=src/shared/greeting.ts
import { Type } from '@sinclair/typebox';
import { defineApiContract } from '@fastang/core/contracts';
export const greetingContract = defineApiContract({
method: 'GET', path: '/api/greet/:name',
schema: {
params: Type.Object({ name: Type.String({ minLength: 1, maxLength: 80 }) }),
response: { 200: Type.Object({ message: Type.String() }, { additionalProperties: false }) },
},
});
```
The path must match the generated API route exactly. Params, query strings and
JSON bodies are validated by Fastify; response schemas control serialization.
Changing the contract does not generate or rename route files.
## Register the Fastify handler
```ts file=src/server/api/greet/[name].get.ts
import { defineContractRoute } from '@fastang/core/contract-route';
import { greetingContract } from '../../../shared/greeting';
export default defineContractRoute(greetingContract, {
handler: async request => ({ message: `Hello, ${request.params.name}!` }),
});
```
`request.params.name` is inferred as a string. Native Fastify route options such
as `preHandler` and `bodyLimit` are supported. Method, URL and schema cannot be
overridden in options. Declare error response statuses in the contract if hooks
or handlers send them.
## Call it through Angular
```ts file=src/app/pages/greet/[name].route.ts
import type { ResolveFn } from '@angular/router';
import { injectApiClient } from '@fastang/core/http-client';
import type { ApiSuccess } from '@fastang/core/contracts';
import { greetingContract } from '../../../shared/greeting';
const greeting: ResolveFn> = route =>
injectApiClient().request(greetingContract, { params: { name: route.paramMap.get('name')! } });
export default { title: 'Greeting · FastAng', resolve: { greeting } };
```
`injectApiClient()` uses Angular HttpClient, so interceptors, SSR local dispatch
and hydration caching continue to apply. `ApiSuccess` is the union of successful
2xx response bodies. HTTP failures remain native `HttpErrorResponse` objects;
they do not become successful values.
Visit `/greet/world`: the existing page receives `{ message: 'Hello, world!' }`
through its resolver input. The [rendering guide](https://fastang.dev/en/docs/rendering.md) explains the SSR
and hydration flow.
## Boundaries
Use `schema.querystring` for a query object and pass `query` to the client;
use `schema.body` and pass `body` for JSON input. Every declared input schema
requires its corresponding request field, even if its properties are optional.
Path params are encoded; flat query arrays become repeated keys. Nested query
objects are not supported.
Contracts currently support GET, POST, PUT, PATCH and DELETE with static/named
local `/api/...` paths and JSON body responses. For wildcard routes, uploads,
streaming, progress or full response observation, use native HttpClient and
`defineApiRoute()`. Browser response types are not runtime response validation:
client/server version skew still needs application handling. See the
[SSR HTTP contract](https://github.com/daclab-dev/fastang/blob/main/docs/ssr-http.md#requests-and-sessions)
for credential and cache behavior.
## Static route checks
From `0.1.0-alpha.4`, FastAng includes a route/contract diagnostic command:
```sh
npx fastang check
npx fastang check --json
npx fastang check --strict --project tsconfig.node.json
```
The checker defaults to `tsconfig.node.json`, falling back to `tsconfig.json`.
It uses TypeScript module resolution, including inherited compiler settings,
`paths`, aliases and re-exports. It starts from file-based API routes and checks
only the contract actually passed to `defineContractRoute`; unused contracts
are not treated as missing routes. It never imports application modules, loads
FastAng/Vite configuration, emits files or connects to application services.
Literal strings, const references, object literals and resolvable spreads are
supported. Function calls that construct contracts, mutable variables, wrappers
and unresolved imports produce `FA1003` (unverified), rather than a successful
verification. `--strict` makes these warnings errors. A known method/path mismatch
is `FA1002`; configuration/syntax errors are `FA1000`, and route discovery errors
are `FA1001`. Errors exit with code 1. JSON output has `schemaVersion: 1`, counts
and diagnostics with source positions and a related contract location for
mismatches. `defineApiRoute` needs no contract correspondence check.
This is a source-convention check, not general constant evaluation or a replacement
for TypeScript/Angular checks. Keep method/path definitions declarative; mutations
or side effects elsewhere are not evaluated. Resolving a TS alias here does not
configure runtime aliases. Registration-time validation remains in place.
`fastang build` checks before loading application configuration or clearing build
output. The Vite plugin reuses the checker and reports errors in its terminal and
overlay on route/contract edits; fixing them triggers a full reload. `fastang dev`
checks before restarting the backend and keeps an existing server during an invalid
edit. The standalone CLI delegates `check` to the app's installed core: both need
a release containing this command. Use `0.1.0-alpha.4` or later for both packages.
---
Source: https://fastang.dev/en/docs/rendering.md
# Data, SSR and prerendering
Load data with HttpClient and choose which URLs can be generated during the build.
## An Angular resolver
In the starter, the resolver reads the `name` parameter and calls the local API. These are the two complete files already present in the project.
```ts file=src/app/pages/greet/[name].route.ts
import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import type { ResolveFn } from '@angular/router';
const greeting: ResolveFn<{ message: string }> = route =>
inject(HttpClient).get<{ message: string }>(
'/api/greet/' + encodeURIComponent(route.paramMap.get('name')!),
);
export default {
title: 'Greeting · FastAng',
resolve: { greeting },
};
```
```ts file=src/app/pages/greet/[name].page.ts
import { Component, input } from '@angular/core';
@Component({
template: `
SERVER RENDERED
{{ greeting().message }}
The Angular resolver calls a validated Fastify endpoint.
Hydration reuses its response.
`,
})
export default class GreetingPage {
readonly greeting = input.required<{ message: string }>();
}
```
The starter enables `withComponentInputBinding()`, so data resolved under `greeting` reaches the input with the same name.
From `0.1.0-alpha.1`, response cookies can be forwarded only by explicit server dispatch, and credentialed API responses are excluded from the default hydration cache. Internal API redirects remain explicit. See the [SSR HTTP contract](https://github.com/daclab-dev/fastang/blob/main/docs/ssr-http.md#requests-and-sessions).
## What happens during SSR
The renderer uses public Angular APIs. An HttpClient call to `/api/...` goes through `Fastify.inject()` in the same application, after interceptors. There is no need to start another server or call localhost from the renderer.
For eligible responses, Angular's HTTP transfer cache passes the data to the browser during hydration. External-origin requests use the fetch backend. The bridge forwards cookies, authorization and `x-request-label` from the incoming request; each render has a separate context. Automatically propagating API `Set-Cookie` headers and redirects into HTML responses is not implemented.
## Explicit prerendering
Replace `fastang.config.ts` with this file to also generate the public greeting during the build.
```ts file=fastang.config.ts
import { defineConfig } from '@fastang/core';
export default defineConfig({
prerender: ['/about', '/greet/world'],
vercel: { functions: 'routes' },
});
```
List concrete URLs, including for parameterized pages. In this alpha, `/`, `/api` and asset paths are reserved; prerender paths cannot include query strings. A response other than HTTP 200 fails the build. Do not list URLs that depend on cookies or personal data.
`/greet/world` uses generated HTML; `/greet/Ada` continues to use SSR. Both use SSR in development. SSR responses are private and have no shared HTML cache; ISR is not implemented.
Continue with [errors and redirects](https://fastang.dev/en/docs/errors.md). Read about the lifecycle of [Angular resolvers](https://angular.dev/guide/routing/data-resolvers).
## SSR deadlines (0.1.0-alpha.6)
The server allows 30 seconds for template loading and rendering. A timeout
returns HTTP 504 with a private, non-cacheable response and discards queued
cookies and redirects. Disconnecting the client or closing the server cancels
the request's rendering work.
For a custom server, set `renderTimeoutMs` in the options passed to `createServer`
from `@fastang/core/server`. It accepts a positive integer in milliseconds;
it is not a `fastang.config.ts` option. Direct API requests and prerendered files
are outside this deadline. Your hosting provider may impose a shorter limit.
Server-side code can pass `context.signal` to cancellable operations such as
`fetch`. Internal API dispatch inherits the render's signal and accepts a
separate `signal` in its dispatch options. Angular bootstrap and stability waits
are interrupted, then Angular destroys the request's providers. Cancellation
does not undo side effects or interrupt synchronous work. Custom renderers,
database operations and asynchronous serialization hooks must cooperate with
cancellation; already-running serialization hooks may outlive the HTTP response.
See the [SSR HTTP contract](https://github.com/daclab-dev/fastang/blob/main/docs/ssr-http.md#render-deadlines-and-cancellation-010-alpha6)
for the lifecycle and its limits.
---
Source: https://fastang.dev/en/docs/router-resources.md
# Reactive route data with Signals
Use native Angular Route Resources with FastAng file-based routing and typed API contracts.
## Status and setup
This example targets Angular 22.2.3. `withRouterResources()` and route `resources`
are developer preview APIs. They are opt-in; the generated starter keeps stable
resolvers. FastAng remains zoneless. This example adds no FastAng resource wrapper.
Install `@sinclair/typebox@^0.34.41` in your starter. Copy the complete files below,
then run `npm run build` and open `/resources/Ada` after starting the server.
For pnpm use `pnpm add @sinclair/typebox@^0.34.41` and `pnpm run build`.
The configuration replaces `app.config.ts`. In an existing app merge the providers
and choose your own navigation-error policy. The example replaces the greeting
endpoint with its contract-based equivalent; keep the existing resolver and page.
## Application configuration
Angular owns routing and resources. FastAng passes route metadata through to the
native router. The error handler below sends failed navigations to a demo error
page; it does not infer HTTP 404 or provide authentication.
```ts file=src/app/app.config.ts
import { inject, type ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideClientHydration } from '@angular/platform-browser';
import {
provideRouter, withComponentInputBinding, withRouterResources,
withNavigationErrorHandler, RedirectCommand, Router,
} from '@angular/router';
import { createRoutes } from './fastang.routes';
export function createAppConfig(): ApplicationConfig {
return {
providers: [
provideRouter(
createRoutes(),
withComponentInputBinding(),
withRouterResources(),
// Demo policy: show a dedicated page if a blocking resource fails.
withNavigationErrorHandler(() =>
new RedirectCommand(inject(Router).parseUrl('/resource-error')),
),
),
provideHttpClient(withFetch()),
provideClientHydration(),
],
};
}
```
## Shared contract and endpoint
```ts file=src/shared/greeting.ts
import { Type } from '@sinclair/typebox';
import { defineApiContract } from '@fastang/core/contracts';
export const greetingContract = defineApiContract({
method: 'GET', path: '/api/greet/:name',
schema: {
params: Type.Object({ name: Type.String({ minLength: 1, maxLength: 80 }) }),
response: { 200: Type.Object({ message: Type.String() }, { additionalProperties: false }) },
},
});
```
```ts file=src/server/api/greet/[name].get.ts
import { defineContractRoute } from '@fastang/core/contract-route';
import { greetingContract } from '../../../shared/greeting';
export default defineContractRoute(greetingContract, {
handler: async request => ({ message: `Hello, ${request.params.name}!` }),
});
```
## Reactive route metadata
```ts file=src/app/pages/resources/[name].route.ts
import { httpResource } from '@angular/common/http';
import { computed, inject } from '@angular/core';
import { Router, type Route } from '@angular/router';
import { apiUrl, type ApiSuccess } from '@fastang/core/contracts';
import { greetingContract } from '../../../shared/greeting';
export default {
title: 'Reactive greeting · FastAng',
canActivate: [route => route.queryParamMap.get('preview') === 'blocked'
? inject(Router).createUrlTree(['/about'])
: true],
runGuardsAndResolvers: 'paramsOrQueryParamsChange',
resources: ({ params }) => {
// Keep unrelated query navigation from rebuilding the HTTP request.
const name = computed(() => params()['name']);
return {
greeting: httpResource>(() =>
apiUrl(greetingContract, { params: { name: name() } }),
),
};
},
} satisfies Pick;
```
## Signal Inputs and error page
```ts file=src/app/pages/resources/[name].page.ts
import { Component, computed, inject, input } from '@angular/core';
import { Router, RouterLink } from '@angular/router';
import type { ApiSuccess } from '@fastang/core/contracts';
import type { greetingContract } from '../../../shared/greeting';
@Component({
imports: [RouterLink],
template: `
ROUTER RESOURCES / DEVELOPER PREVIEW
{{ greeting().message }}
Route parameter: {{ name() }}
@if (mode() === 'details') {
A typed contract builds the URL. Angular loads the resource before activation.
The navigation failed. Try a valid name or return to your application.
Try again
`,
})
export default class ResourceErrorPage {}
```
## Behavior to verify
- Load `/resources/Ada`: SSR includes `Hello, Ada!`; the blocking resource value
reaches the `greeting` input before the component activates.
- Navigate to Grace: `name` changes and the resource loads the new API URL.
- Choose Details: `mode` changes without reloading the greeting. Only `name` is a
dependency of the resource URL. Missing or unrecognized `mode` means `summary`.
- Choose API failure: validation rejects the oversized name. The navigation error
handler redirects to `/resource-error`; Try again recovers with a valid name.
- Choose Redirect: the guard sends navigation to `/about` before loading the resource.
Signal Input types do not validate URL values. The `mode` transform normalizes
its query input; the API validates the name through the contract. `apiUrl` infers
URL inputs, and `ApiSuccess` supplies the response type; neither validates the
response in the browser.
The `computed` isolates the name from replacement parameter objects during query
navigation. `runGuardsAndResolvers` also reruns the guard when query values change.
## SSR and compatibility boundaries
`httpResource` uses HttpClient, including the local SSR dispatch and native HTTP
transfer cache. Eligible public responses are reused during hydration. Requests
carrying Cookie or Authorization are excluded and may fetch again in the browser.
No shared HTML cache is introduced. Resource instances must be created inside
the route factory; never export a resource instance as module-level shared state.
This example covers blocking `httpResource` route resources. It does not claim
blanket compatibility for `nonBlocking`, arbitrary `resource` loaders or direct
`fetch`, which may bypass the HTTP bridge. Keep the stable
[resolver approach](https://fastang.dev/en/docs/rendering.md) when you do not want preview APIs.
The repository browser suite tests the packaged example across Chromium, Firefox
and WebKit, including mobile emulation. See the
[Angular Route Resources guide](https://angular.dev/guide/routing/data-fetching-with-resources)
for native API semantics.
---
Source: https://fastang.dev/en/docs/errors.md
# Errors, redirects and 404s
Distinguish API errors from HTML responses and use Angular guards for redirects.
## Handle resolver errors
Replace the greeting route file with this example. The guard runs before route activation; the resolver handles API failures without leaving navigation incomplete.
```ts file=src/app/pages/greet/[name].route.ts
import { REQUEST_CONTEXT, inject } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Router, type CanActivateFn, type ResolveFn, type Route } from '@angular/router';
import { catchError, of } from 'rxjs';
import type { RenderContext } from '@fastang/core';
const previewGuard: CanActivateFn = route => route.queryParamMap.get('preview') === 'blocked'
? inject(Router).createUrlTree(['/about'])
: true;
const greeting: ResolveFn<{ message: string }> = route => {
const context = inject(REQUEST_CONTEXT) as RenderContext | null;
return inject(HttpClient)
.get<{ message: string }>(
'/api/greet/' + encodeURIComponent(route.paramMap.get('name')!),
)
.pipe(
catchError((error: HttpErrorResponse) => {
if (context) context.status = error.status === 404 ? 404 : 502;
return of({ message: 'Greeting unavailable.' });
}),
);
};
export default {
title: 'Greeting · FastAng',
canActivate: [previewGuard],
resolve: { greeting },
runGuardsAndResolvers: 'paramsOrQueryParamsChange',
} satisfies Pick;
```
Open `/greet/Ada?preview=blocked`: SSR returns HTTP 302 to `/about`, a `Location` header and an empty body. In the browser Angular navigates to the destination. This guard is a demo: it does not implement authentication or protect an API directly.
If the API fails, the page displays `Greeting unavailable.`. During server rendering, the context sets HTTP 404 for an API 404 and HTTP 502 for other failures. In the browser, `REQUEST_CONTEXT` is null: client navigation cannot change the HTTP status of an already received document.
## A 404 page
The starter already includes this terminal catch-all.
```ts file=src/app/pages/[...slug].page.ts
import { Component, REQUEST_CONTEXT, inject } from '@angular/core';
import { RouterLink } from '@angular/router';
import type { RenderContext } from '@fastang/core';
@Component({
imports: [RouterLink],
template: `
Page not found
Back home
`,
})
export default class NotFoundPage {
constructor() {
const context = inject(REQUEST_CONTEXT) as RenderContext | null;
if (context) {
context.status = 404;
}
}
}
```
Displaying the text 404 alone does not change the response status. The SSR context makes the not-found page return HTTP 404.
## Redirect limits
The renderer detects the router's final destination. Rendering reaches the destination page before emitting the redirect; it does not stop bootstrap early. The bridge accepts local destinations and rejects external or ambiguous URLs. Redirects from internal API calls are not automatically propagated into the HTML response.
Continue with [server configuration and plugins](https://fastang.dev/en/docs/configuration.md).
---
Source: https://fastang.dev/en/docs/configuration.md
# Configuration, plugins and environment
Extend the server with Fastify and keep public settings separate from backend secrets.
Configuration validation is available from `0.1.0-alpha.1`.
`defineConfig()` validates configuration at runtime. The build also validates plain default exports before replacing `dist/`. Unknown options, invalid types, reserved prerender paths (`/`, `/api`, `/assets`, `/__fastang`) and conflicting destinations such as `/about` plus `/about/` fail with an explicit error. Prerender paths contain only ASCII letters, digits, underscores, hyphens and slashes; query strings and fragments are not supported.
From `0.1.0-alpha.3`, `fastang dev` also validates plain configuration
exports before loading application plugins or opening the server. Invalid settings
stop the current server; the development supervisor keeps watching. Correct and
save `fastang.config.ts` to restart automatically. Development still renders
prerender paths on demand; static HTML is generated only during the build.
## Register a hook
Replace `src/server/plugins.ts` with this file. The hook adds a header to every request handled by Fastify.
```ts file=src/server/plugins.ts
import type { FastifyInstance } from 'fastify';
export async function configureServer(app: FastifyInstance): Promise {
app.addHook('onRequest', async (_request, reply) => {
reply.header('x-app-name', process.env['APP_NAME'] ?? 'my-fastang-app');
});
}
```
This entry is used by the Node server and Vercel API/SSR functions. Files served directly by Vercel's CDN do not pass through Fastify hooks. You can also use `app.register()` for Fastify 5 compatible plugins. Install additional plugins in your project: they are not included automatically.
## Server variables
```bash
PORT=4000 HOST=127.0.0.1 APP_NAME=my-app npm run dev
```
The example uses POSIX shell syntax. Supply variables through the shell or deployment platform. The alpha does not automatically load `.env` files into the Fastify process. `PORT` must be valid; the default port is 3278 and the default host is `127.0.0.1`.
Vite's `VITE_` variables are intended for the client and can enter the browser bundle. Do not use them for server secrets. Read server variables from `process.env` in APIs and plugins.
## The two configuration files
`fastang.config.ts` contains explicit prerender URLs and Vercel adapter options. `vite.config.ts` uses `defineFastangConfig()` from `@fastang/core/vite`: this includes Angular compilation, route generation and the server/client boundary.
Do not import `src/server` code, `.server.ts` files or server runtime APIs into browser components. The bundle check rejects these imports even through another module. Shared types should live in modules that do not load server dependencies.
See [Fastify hooks](https://fastify.dev/docs/latest/Reference/Hooks/) and [Vite variables](https://vite.dev/guide/env-and-mode). Continue with [deployment](https://fastang.dev/en/docs/deployment.md).
## Development logging
From `0.1.0-alpha.5`, development defaults to `warn` for both Fastify and Vite during `fastang dev`.
The listening address remains visible. Routine HTTP request/completion logs and
successful route-check summaries are omitted; warnings, errors and failed checks
remain visible. Production logger configuration is unchanged.
Set `FASTANG_LOG_LEVEL=info` or `FASTANG_LOG_LEVEL=debug` in the process environment
when investigating requests. Accepted values are `trace`, `debug`, `info`, `warn`,
`error`, `fatal` and `silent`. Vite maps trace/debug to info and fatal to error.
This controls framework loggers, not application `console.log` calls or package
manager output. The startup address is retained even with `silent`.
`fastang check --quiet` suppresses only a successful text summary; diagnostics
still print, and `--json` always returns the JSON report. The dev supervisor uses
this option for checks before backend restarts. These options are available from `0.1.0-alpha.5`.
---
Source: https://fastang.dev/en/docs/upgrading.md
# Update an existing project
Update the framework dependency in your existing app. The generator creates new
projects; running it again is not an update procedure and it refuses existing
destinations. Your pages, APIs, styles and business code remain application files.
## Choose the target release
Commit the current app and its lockfile, read the target release notes and record
the installed version. This example upgrades `0.1.0-alpha.1` to `0.1.0-alpha.2`.
```sh
npm ls @fastang/core
npm view @fastang/core@0.1.0-alpha.2 peerDependencies engines --json
```
Check Node and the target peer dependencies before installing. If Angular, Vite,
TypeScript or other peer ranges change, align the application's dependencies too.
Keep all pinned Angular packages aligned. If Node requirements change, update
the runtime, project engines and CI/deployment settings together. Do not use `--force` or
`--legacy-peer-deps` to turn a dependency mismatch into an apparently successful
upgrade. Alpha releases may change APIs even within the same `0.1.0` prerelease
line. Read [the release notes](https://github.com/daclab-dev/fastang/releases).
## Install and verify
```sh
npm install --save-exact @fastang/core@0.1.0-alpha.2
npm run generate
npm run build
npm run build:vercel
```
Run the application's own tests and checks for any deployment you use; the Vercel
command is only needed for a Vercel app. Review and commit `package.json`,
`package-lock.json` and intentional migration changes together. Deploy the checked
commit. The generator is not normally installed in the generated app and does
not need to be updated there.
For a future stable `1.0.0` to `1.2.0` upgrade, use the same process with `1.2.0`
as the explicit target. This is an example, not a currently published release.
Stable minor releases are intended to preserve compatibility; major changes
require migration instructions. During alpha, use the release notes rather than
assuming that every minor-looking change is compatible.
## Application bindings and migrations
Installing the package updates the framework, not the copies of starter files in
your project. In particular, `src/server/index.ts`, `start.ts`, `api-entry.ts`,
`vercel-entry.ts`, Angular bootstrap/configuration and Vite/FastAng config belong
to the app. If a release changes those bindings, apply its documented migration
and keep your custom behavior.
Starting with `0.1.0-alpha.3`, the separate `@fastang/cli` package owns
project creation and upgrades. It also works with older installed cores:
```sh
npx --package=@fastang/cli@0.1.0-alpha.6 fastang-cli update --to alpha
npx --package=@fastang/cli@0.1.0-alpha.6 fastang-cli update --to alpha --yes
```
The first command shows a plan; the second applies the npm installation.
`--to latest` follows npm's `latest` tag, which may differ from `alpha`.
The plan compares Node, installed peers and the target release. The manifest,
lockfile and installed core must agree: reconcile differences using `npm install`
or `npm ci` as instructed and review the diff. Incompatibilities block applying;
downgrades require `--allow-downgrade`. An identical installed version with
consistent state does not trigger installation. Source migrations remain manual.
npm installation can execute scripts and failures are not automatically rolled back. Do not replace the entire `src/` directory with a new template. To compare
a target starter, generate a separate app outside the existing project:
```sh
cd ..
npm create fastang@0.1.0-alpha.2 fastang-comparison -- --install
```
Compare only the relevant bindings and configuration, then apply the necessary
changes manually. `fastang generate` rewrites generated registration modules;
it is not a general migration tool. Moving to `alpha.2` adds optional features:
existing endpoints do not need to become contracts and TypeBox is not required
unless you adopt shared schemas.
## pnpm updates
Run the update from your application directory with pnpm 10 or 11. First commit
your current work, including `package.json` and `pnpm-lock.yaml`.
To preview an update to the newest alpha release:
```sh
pnpm --package=@fastang/cli@alpha dlx fastang-cli update --to alpha
```
This downloads and runs the CLI without installing it globally. It shows the
installed version, the target version and any incompatible Node or application
dependencies. Your project files remain unchanged at this stage.
After reviewing the plan and the [release notes](https://github.com/daclab-dev/fastang/releases),
apply the update:
```sh
pnpm --package=@fastang/cli@alpha dlx fastang-cli update --to alpha --yes
```
FastAng uses pnpm to update `@fastang/core` and the lockfile. Your pages,
endpoints, styles and configuration remain application files: apply any source
migrations described in the release notes yourself.
Check the result with a build and your application's tests:
```sh
pnpm run build
```
For Vercel deployments, also run `pnpm run build:vercel`. Review the changes and
commit `package.json`, `pnpm-lock.yaml` and any source migrations together.
To choose a specific release, replace `alpha` after `--to` with its version,
for example `--to 0.1.0-alpha.6`.
### If the update stops
The CLI identifies the check that failed. If the installation differs from the
lockfile, restore it with `pnpm install --frozen-lockfile`. If you have just
changed dependencies in `package.json`, run `pnpm install` instead and review
the updated lockfile before trying again.
Keep one lockfile and a `packageManager` declaration that matches pnpm.
Automatic updates cover standalone applications; for monorepos with a shared
lockfile, update dependencies through the workspace's tools.
## Documentation versions and rollback
Use the version selector above the guides. Versioned URLs retain the selected
version; sidebar links, pagination, relative guide links and language switching
stay in it. If a guide is absent in the selected release, changing version opens
that release's index. An unknown version or guide returns HTTP 404 instead of
silently showing the newest instructions.
Before v1, the selector exposes only the current documentation, updated in place.
Older alpha guides are not retained on the site; their sources remain in Git tags.
Versioned URLs for retired alpha guides return 404. Documentation snapshots
will start with stable v1 releases. `/docs` opens the current guides.
To roll back, restore the previous application commit and lockfile, run `npm ci` or
`pnpm install --frozen-lockfile`, depending on the project’s package manager,
and redeploy that checked state. Simply downgrading the package does not undo
manual source changes or external database/service migrations.
---
Source: https://fastang.dev/en/docs/reference.md
# Public API and support reference
This reference describes `0.1.0-alpha.6`. All FastAng APIs are experimental:
being exported makes an API public, not stable across alpha releases. Pin the
core and generator to matching versions when reproducing a project.
## Public package entry points
| Import | Context | API |
| --- | --- | --- |
| `@fastang/core` | Node | `defineApiRoute`, config, server factories, Node handler, listen, rendering types |
| `@fastang/core/api` | Node | `defineApiRoute`, `ApiRouteOptions` |
| `@fastang/core/config` | Configuration | `defineConfig`, `validateConfig`, `validateVercelOptions` and config types |
| `@fastang/core/server` | Node | `createServer`, `createApiServer`, `staticFile`, renderer/server option types |
| `@fastang/core/http-server` | Node | `createHttpServer`, `HttpServerOptions` |
| `@fastang/core/node-handler` | Node | `createNodeHandler` |
| `@fastang/core/listen` | Node | `listen`, `DEFAULT_PORT` |
| `@fastang/core/angular` | Angular SSR | `createRenderer` |
| `@fastang/core/render-context` | Types | `RenderContext`, `ApiDispatchOptions`, `ApiDispatchResult` |
| `@fastang/core/contracts` | Browser + SSR | `defineApiContract`, `apiUrl` and inferred contract types |
| `@fastang/core/contract-route` | Node | `defineContractRoute` |
| `@fastang/core/http-client` | Angular browser + SSR | `createApiClient`, `injectApiClient`, `ApiRequestOptions` |
| `@fastang/core/redirect` | Browser + SSR | `normalizeRedirectLocation` |
| `@fastang/core/vite` | Build | `defineFastangConfig` |
| `@fastang/core/routes` | Tooling | Discovery, page trees, generation, inspection and route types |
| `@fastang/core/vite-plugin` | Tooling | `fastang`, `isServerModule` |
| `@fastang/core/adapters/vercel-plan` | Tooling | Function planning, route patterns and deployment plan types |
| `@fastang/core/adapters/vercel` | Tooling | `buildVercelOutput` |
Use these package paths instead of importing `dist/`, `src/` or internal command
modules. The tooling entry points expose implementation-oriented APIs and are
especially likely to change. A module being present in a tarball does not make
it an independently supported import path.
The root runtime import is server-only. For Angular code, use `/http-client` and
`/contracts` as appropriate. `/angular` configures server rendering, not browser
bootstrap. Use `import type` for server/rendering types consumed by Angular so
those imports are erased. The Vite boundary check rejects runtime server imports
in browser bundles, including through another module.
## Runtime and dependency matrix
| Package | Accepted range |
| --- | --- |
| Node.js | `^22.22.3 / ^24.15.0 / >=26.0.0` |
| `@angular/common` | `22.2.3` |
| `@angular/compiler` | `22.2.3` |
| `@angular/core` | `22.2.3` |
| `@angular/platform-browser` | `22.2.3` |
| `@angular/platform-server` | `22.2.3` |
| `@angular/router` | `22.2.3` |
| `fastify` | `^5.12.7` |
| `rxjs` | `^7.8.2` |
| `tslib` | `^2.8.1` |
| `vite` | `8.3.4` |
| `typescript` | `~6.0.2` |
| `@angular/compiler-cli` | `22.2.3` |
| `@angular/build` | `22.2.3` |
| `@sinclair/typebox` | `^0.34.41` |
The Node.js alternatives in the first row are separated by `/`. These are the
package engine and peer ranges, not a claim that every future matching version
has been tested. All Angular peers are pinned together. TypeBox is an optional
peer: install it when using shared contracts. Other peers are required by the
application/tooling setup. Runtime dependencies owned by the core are installed
with it; the table is not a list of every transitive package.
CI runs the production app on Linux with Node 22.22.3, 24.15.0 and the Node 26
line. Browser tests cover Chromium, Firefox and WebKit, plus Pixel 7 Chromium and
iPhone 13 WebKit emulation. They verify the generated starter and authentication
example; emulation is not a real-device test. macOS, Windows, Bun, Deno and other
Node versions do not have equivalent automated coverage in this repository.
## Deployment and rendering scope
| Target | Current scope |
| --- | --- |
| Node server | Fastify HTTP server, SSR, APIs, explicit public prerender URLs, graceful signal shutdown |
| Vercel | Build Output API v3, Node.js 24 functions, shared or per-route functions, CDN assets and prerendered HTML |
| Edge runtimes | No adapter |
| Static-only export | No complete server-free application export |
| ISR and revalidation | Not implemented |
| Angular HTML streaming | Complete HTML is produced before the SSR response is sent |
By default, SSR responses are private and uncached by shared caches; prerendered HTML is
public. Concrete prerender URLs are listed in config; `/` and runtime namespaces
remain reserved. Development renders those pages on demand. See
[rendering](https://fastang.dev/en/docs/rendering.md) for data and cache behavior and [deployment](https://fastang.dev/en/docs/deployment.md)
for output layout and Vercel configuration.
Node/Vercel packaging checks run in CI, and the documentation site is deployed
on Vercel. This is not blanket compatibility for every plugin or deployment:
native addons, dynamically read files and external services need verification in
the consuming app. The optional session example supplies its own native-addon
packaging step. Edge support cannot be inferred from the use of Vite or Angular.
## CLI contract
```sh
npx fastang --help
npx fastang generate
npx fastang routes --json
npx fastang dev
npm run build
npm run build:vercel
```
`generate` writes discovered Angular/API registration modules. `routes` reads the
file map without executing application modules or writing files; `--json` emits
a `schemaVersion: 1` document. `dev` validates config, starts Vite/Fastify and
restarts the backend when watched server/shared/config files change. The default
port is 3278; `PORT` and `HOST` override it.
The starter's build scripts run Angular/TypeScript checks before calling
`fastang build` or `fastang build --vercel`. The raw CLI build command does not
replace those checks. Build configuration validation happens before replacing
`dist/`. `fastang.config.ts` and the application bindings supplied by the starter
are required; this is not a drop-in CLI for arbitrary Angular workspaces.
## Ownership and compatibility boundaries
FastAng owns file discovery, Angular/Fastify integration, local SSR HTTP dispatch
and adapter output. Angular owns routing, DI, HttpClient and hydration. Fastify
owns API hooks, parsing, validation, serialization and plugin behavior. Typed
contracts add schema-derived types; they do not introduce a second router or RPC
transport. The [typed API guide](https://fastang.dev/en/docs/typed-api.md) documents their narrower JSON scope.
Session policy, identity providers, persistent storage, authorization, background
jobs and distributed state belong to the application or its services. Shared and
per-route Vercel functions do not share process memory. Read the
[SSR HTTP contract](https://github.com/daclab-dev/fastang/blob/main/docs/ssr-http.md#requests-and-sessions)
for credentials, cookies and redirects.
## CLI route checks
`fastang check [--project ] [--json] [--strict]` checks route/contract correspondence without application execution. See [typed APIs](https://fastang.dev/en/docs/typed-api.md#static-route-checks).
---
Source: https://fastang.dev/en/docs/deployment.md
# Deploy to Node and Vercel
Use the same project for a persistent Node server or Vercel functions.
## Node server
```bash
npm ci
npm run build
HOST=0.0.0.0 PORT=3278 npm start
```
Use `npm ci` when the project already has a `package-lock.json`; the generator creates it with `--install`. Otherwise run `npm install` first and commit the lockfile.
The process uses the alpha's supported Node runtime. Keep the complete `dist/server`, `dist/client` and `dist/prerender.json` structure with installed runtime dependencies; do not copy only `index.js`. The server serves assets and prerendered URLs from the client directory.
`HOST=0.0.0.0` makes the process reachable on the machine's interfaces, useful behind a reverse proxy or in a container. Configure TLS, the domain and process restarts through your hosting platform. On `SIGTERM`, the server closes Fastify.
## Vercel
The starter includes `vercel.json`. Import the repository into Vercel with these settings.
| Setting | Value |
| --- | --- |
| Framework preset | Other |
| Build command | `npm run build:vercel` |
| Output directory | No override |
| Project Node version | 24.x |
To check the output before deploying:
```bash
npm run build:vercel
```
The command generates `.vercel/output` in the Build Output API format. Do not upload these files to a public website root or set `dist/client` as the output: the root template must remain private to the SSR renderer.
## Rendering and functions
The starter uses `vercel: { functions: 'routes' }`: the adapter creates dedicated page and endpoint functions plus fallbacks. Static files and prerendered pages use the CDN. Functions reuse Fastify initialization between warm invocations without starting a TCP listener.
Configure server variables in Vercel and redeploy to apply changes. Do not use client `VITE_` variables for secrets. This alpha does not implement ISR or shared SSR HTML caching.
## Verify the result
Check `/`, `/greet/world`, `/api/health`, `/about` and an unknown URL. Also try the counter and browser navigation. A successful build does not replace checking the actual deployment.
Return to [configuration and environment](https://fastang.dev/en/docs/configuration.md) for server plugins and variables.