Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default defineConfig({
{ label: 'Versioning', slug: 'core-concepts/versioning' },
{ label: 'Dependency Injection', slug: 'core-concepts/dependency-injection' },
{ label: 'Providers', slug: 'core-concepts/providers' },
{ label: 'Macroable', slug: 'core-concepts/macroable' },
{ label: 'Events', slug: 'core-concepts/events' },
{ label: 'Lifecycle Hooks', slug: 'core-concepts/lifecycle-hooks' },
{ label: 'Configuration', slug: 'core-concepts/configuration' },
Expand All @@ -80,8 +81,12 @@ export default defineConfig({
{ label: 'Validation', slug: 'guides/validation' },
{ label: 'Guards', slug: 'guides/guards' },
{ label: 'Middleware', slug: 'guides/middleware' },
{ label: 'Rate Limiting', slug: 'guides/rate-limiting' },
{ label: 'Error Handling', slug: 'guides/error-handling' },
{ label: 'Environment Typing', slug: 'guides/environment-typing' },
{ label: 'Domain Routing', slug: 'guides/domain-routing' },
{ label: 'Signed URLs', slug: 'guides/signed-urls' },
{ label: 'Streaming Responses', slug: 'guides/streaming' },
],
},
{
Expand All @@ -90,6 +95,7 @@ export default defineConfig({
{ label: 'Queues', slug: 'integrations/queues' },
{ label: 'Cron Jobs', slug: 'integrations/cron-jobs' },
{ label: 'Caching', slug: 'integrations/caching' },
{ label: 'Feature Flags', slug: 'integrations/feature-flags' },
{ label: 'Storage', slug: 'integrations/storage' },
{ label: 'Email', slug: 'integrations/email' },
{ label: 'Internationalization', slug: 'integrations/i18n' },
Expand Down Expand Up @@ -121,10 +127,26 @@ export default defineConfig({
{ label: 'Seeders', slug: 'framework/seeders' },
{ label: 'Factories', slug: 'framework/factories' },
{ label: 'Auth', slug: 'framework/auth' },
{ label: 'RBAC', slug: 'framework/rbac' },
{ label: 'Access Control', slug: 'framework/access-control' },
{ label: 'Auth Guard', slug: 'framework/auth-guard' },
],
},
{
label: '@stratal/inertia',
items: [
{ label: 'Overview & Setup', slug: 'inertia/overview' },
{ label: 'Pages & Rendering', slug: 'inertia/pages-and-rendering' },
{ label: 'Shared Data & Props', slug: 'inertia/shared-data-and-props' },
{ label: 'Flash Messages', slug: 'inertia/flash-messages' },
{ label: 'SSR', slug: 'inertia/ssr' },
{ label: 'Forms & Validation', slug: 'inertia/forms-and-validation' },
{ label: 'Modals', slug: 'inertia/modals' },
{ label: 'React Hooks', slug: 'inertia/react-hooks' },
{ label: 'Vite Plugin', slug: 'inertia/vite-plugin' },
{ label: 'Testing', slug: 'inertia/testing' },
{ label: 'CLI Commands', slug: 'inertia/cli-commands' },
],
},
{
label: 'API Reference',
attrs: { target: '_blank' },
Expand Down
45 changes: 35 additions & 10 deletions src/content/docs/core-concepts/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: Configuration
description: Typed config namespaces, dot-notation access, schema validation, and runtime overrides with registerAs and ConfigService.
---

import { Aside } from '@astrojs/starlight/components';
import { Aside, LinkCard } from '@astrojs/starlight/components';

The configuration system gives you a structured, type-safe way to manage application settings in Stratal. It is environment-agnostic the framework never reads environment variables directly. Instead, your application defines **config namespaces** that extract values from the Cloudflare `Env` object and expose them through a centralized `ConfigService`.
The configuration system gives you a structured, type-safe way to manage application settings in Stratal. It is environment-agnostic - the framework never reads environment variables directly. Instead, your application defines **config namespaces** that extract values from the Cloudflare `Env` object and expose them through a centralized `ConfigService`.

Key capabilities:

Expand All @@ -14,13 +14,38 @@ Key capabilities:
- Optional Zod schema validation at startup
- Runtime overrides via `config.set()` for request-scoped values

## Application config versus namespaced config

Two layers of configuration coexist, and they serve different purposes:

- **Application config** is the object you pass to the `Stratal` constructor. It tunes framework behaviour at boot: `versioning`, `trailingSlash`, `logging`, and the exception handler. The `trailingSlash` key accepts either a bare mode (`'ignore' | 'always' | 'never'`) or a `{ mode, exclude }` object that exempts specific paths from canonicalization.

```typescript
export default new Stratal({
module: AppModule,
trailingSlash: { mode: 'always', exclude: ['/auth/oauth2'] },
})
```

- **Namespaced config** is everything described on the rest of this page: your application's own settings, derived from the Cloudflare `Env` object through `registerAs()` and read back through `ConfigService`.

<Aside type="note">
Application config is static and read once at construction. Namespaced config supports schema validation and request-scoped runtime overrides. Reach for namespaced config for anything your application defines.
</Aside>

<LinkCard
title="Routing: trailing slashes and URL generation"
href="/core-concepts/controllers-and-routing/#trailing-slashes"
description="Full reference for the trailingSlash modes, exclusion patterns, and how generated URLs stay consistent with redirects."
/>

## How it works

Configuration flows through three steps:

1. **Define** create config namespaces with `registerAs()`, each receiving the `Env` object and returning a typed config object.
2. **Register** pass all namespaces to `ConfigModule.forRoot()` in your root or shared module. The module resolves the Cloudflare `Env`, calls each factory, optionally validates the merged result, and initializes `ConfigService`.
3. **Use** inject `ConfigService` anywhere and access values with dot-notation paths.
1. **Define** - create config namespaces with `registerAs()`, each receiving the `Env` object and returning a typed config object.
2. **Register** - pass all namespaces to `ConfigModule.forRoot()` in your root or shared module. The module resolves the Cloudflare `Env`, calls each factory, optionally validates the merged result, and initializes `ConfigService`.
3. **Use** - inject `ConfigService` anywhere and access values with dot-notation paths.

```mermaid
flowchart LR
Expand Down Expand Up @@ -101,7 +126,7 @@ export class CoreModule {}
| `validateSchema` | `ZodSchema` | No | A Zod schema to validate the merged config at startup |

<Aside type="note">
Import `ConfigModule.forRoot()` once in your root module or a shared `CoreModule`. Child feature modules do not need to import it they can inject `ConfigService` directly since it is registered in the global DI container.
Import `ConfigModule.forRoot()` once in your root module or a shared `CoreModule`. Child feature modules do not need to import it - they can inject `ConfigService` directly since it is registered in the global DI container.
</Aside>

## Injecting and using ConfigService
Expand Down Expand Up @@ -166,7 +191,7 @@ declare module 'stratal' {
}
```

After this augmentation, `config.get('database.url')` is fully typed your editor will autocomplete valid paths and flag invalid ones at compile time.
After this augmentation, `config.get('database.url')` is fully typed - your editor will autocomplete valid paths and flag invalid ones at compile time.

## Schema validation

Expand Down Expand Up @@ -215,13 +240,13 @@ ConfigValidationError: Configuration validation failed
Use `config.set()` to override config values during a request. This is useful for middleware that adjusts settings based on request context:

```typescript
// In middleware override for this request
// In middleware - override for this request
async handle(ctx: RouterContext, next: () => Promise<void>) {
this.config.set('email.from.name', 'Custom Name')
await next()
}

// In a downstream service reflects the override
// In a downstream service - reflects the override
async sendEmail() {
const fromName = this.config.get('email.from.name') // 'Custom Name'
}
Expand Down Expand Up @@ -351,7 +376,7 @@ To add a new config namespace to your application:
})
```

4. **Set environment variables** add to `.dev.vars.example` for local development and set in production via `wrangler secret put` (sensitive) or `wrangler.jsonc` (non-sensitive).
4. **Set environment variables** - add to `.dev.vars.example` for local development and set in production via `wrangler secret put` (sensitive) or `wrangler.jsonc` (non-sensitive).

## Testing

Expand Down
Loading