# 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.

} @if (navigating()) {

Loading page…

} `, }) export default class ResourceGreetingPage { readonly name = input.required(); readonly greeting = input.required>(); readonly mode = input('summary', { transform: (value: string | undefined) => value === 'details' ? 'details' : 'summary', }); private readonly router = inject(Router); readonly navigating = computed(() => this.router.currentNavigation() !== null); readonly invalidName = 'a'.repeat(81); } ``` ```ts file=src/app/pages/resource-error.page.ts import { Component } from '@angular/core'; import { RouterLink } from '@angular/router'; @Component({ imports: [RouterLink], template: `

Greeting unavailable.

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.