Data
Data on every page
Some data belongs on every page, like the app name or the number of unread messages. You can share it once instead of returning it from every controller.
Share from a middleware
Middleware runs on every request, which makes it a good place for data every page needs:
// src/shared-data.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common'
import { requestState, type AnyRequest } from 'nestjs-mvc'
@Injectable()
export class SharedDataMiddleware implements NestMiddleware {
use(req: AnyRequest, res: unknown, next: () => void) {
requestState(req).shared.appName = 'Acme CRM'
next()
}
}
// src/app.module.ts
@Module({ imports: [MvcModule.forRoot({ vite: {} })] })
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(SharedDataMiddleware).forRoutes('{*path}')
}
}
Every page now gets an appName prop.
Read it anywhere
Shared data ends up in the page props, so any component can read it with usePage(). Usually that's your layout:
import { usePage } from 'nestjs-mvc/react'
export function AppLayout({ children }: { children: React.ReactNode }) {
const { props } = usePage<{ appName: string }>()
return (
<>
<header>{props.appName}</header>
<main>{children}</main>
</>
)
}
<!-- frontend/layouts/AppLayout.vue -->
<script setup lang="ts">
import { usePage } from 'nestjs-mvc/vue'
const page = usePage<{ appName: string }>()
</script>
<template>
<header>{{ page.props.appName }}</header>
<main>
<slot />
</main>
</template>
Data that needs the database
Pass a function instead of a value, and it only runs when a page actually renders:
@Injectable()
export class SharedDataMiddleware implements NestMiddleware {
constructor(private readonly messages: MessagesService) {}
use(req: AnyRequest, res: unknown, next: () => void) {
requestState(req).shared.unread = () => this.messages.countUnread()
next()
}
}
This matters for the logged in user as well. Middleware runs before your guards, so req.user isn't set yet at that point, but a function runs later, after the guards are done.
The logged in user
For the user you can skip the middleware. Tell nestjs-mvc which fields to share and it adds auth.user to every page. Authentication has the details.
MvcModule.forRoot({
vite: {},
auth: { share: (user: User) => ({ id: user.id, name: user.name }) },
})
Share from a single request
Inside a handler, guard or interceptor you can use ViewService.share(), which adds data for the current request only:
@Get()
@View('Dashboard')
dashboard() {
this.view.share('locale', 'en-GB')
return { stats: this.stats.today() }
}
In detail
When a page returns the same name
A page's props are put together in this order, and a later key replaces an earlier one:
errors, the validation errors from the previous request- your shared data, with
auth.useradded - what the handler returns
So a handler that returns title: 'Dashboard' wins over a shared title. The replacement is shallow: if a handler returns its own auth, the shared auth (and the user in it) is gone for that page. And don't share a key called errors, because it replaces the validation errors.
Your own auth object
If you share an auth object yourself, the user is added to it:
requestState(req).shared.auth = { can: { invite: true } }
// the page gets: auth: { can: { invite: true }, user: { id: 1, name: 'Ada' } }
When nobody is logged in, auth.user is null, and so it is when share returns nothing. share also gets the request as its second argument, and it may be async. Whatever it returns ends up in the HTML source of every first load, so pick the fields the page needs and never return the whole user record.
Without auth.share nothing about the user is shared. In development you get a warning when req.user is set but never reaches the page.
Data that must stay fresh
When the page reloads a few props, say router.reload({ only: ['stats'] }), your shared data is left out too, and its functions aren't called. The page keeps the value it had, which is what you want for the app name or the user.
For something that should be up to date after every request, like the unread count, wrap it in always(). It then comes along with every response, even a reload that asks for something else:
import { always, requestState } from 'nestjs-mvc'
requestState(req).shared.unread = always(() => this.messages.countUnread())
Use it on a top level key. An always() inside an object is only sent when that object itself is part of the response. And since its function runs on every request to the page (each deferred prop, each poll tick), keep it cheap.
Other helpers work here too
Shared data is resolved the same way as the props a handler returns. Functions are called at any depth, and defer(), optional() and once() work as well. A list of countries every form needs is a good fit for once(): the browser keeps it across pages.
Error pages
A page you return from errorPages only gets your shared data when it sets shared: true. See Error pages.