N
Nuxtโ€ข4w ago
BenG

Nuxt module for oRPC

I am trying to create a nuxt module which would register the catch all event handler, but I am having hard time to register the routers in it. Anyone has idea how to achieve this?
export function registerRouter(moduleName: string, routerSlice: any, schemas?: Record<string, any>) {
console.log(`๐Ÿ“‹ Registering router for module: ${moduleName}`)

routerRegistry.set(moduleName, routerSlice)

if (schemas) {
schemaRegistry.set(moduleName, schemas)
console.log(`๐Ÿ“‹ Registered ${Object.keys(schemas).length} schemas for module: ${moduleName}`)
}
}
export function getRouter(): any {
const combinedRouter: any = {}

for (const [moduleName, routerSlice] of routerRegistry.entries()) {
combinedRouter[moduleName] = routerSlice
}

console.log(`๐Ÿ”ง Built combined router with modules: ${Array.from(routerRegistry.keys()).join(', ')}`)
return combinedRouter
}
export function registerRouter(moduleName: string, routerSlice: any, schemas?: Record<string, any>) {
console.log(`๐Ÿ“‹ Registering router for module: ${moduleName}`)

routerRegistry.set(moduleName, routerSlice)

if (schemas) {
schemaRegistry.set(moduleName, schemas)
console.log(`๐Ÿ“‹ Registered ${Object.keys(schemas).length} schemas for module: ${moduleName}`)
}
}
export function getRouter(): any {
const combinedRouter: any = {}

for (const [moduleName, routerSlice] of routerRegistry.entries()) {
combinedRouter[moduleName] = routerSlice
}

console.log(`๐Ÿ”ง Built combined router with modules: ${Array.from(routerRegistry.keys()).join(', ')}`)
return combinedRouter
}
And the handler:
export default defineEventHandler(async (event) => {
console.log('๐Ÿš€ RPC Handler - Handling oRPC request:', event.node.req.method, event.node.req.url)
const router = getRouter()
console.log('๐Ÿ”ง Available router modules:', Object.keys(router))
export default defineEventHandler(async (event) => {
console.log('๐Ÿš€ RPC Handler - Handling oRPC request:', event.node.req.method, event.node.req.url)
const router = getRouter()
console.log('๐Ÿ”ง Available router modules:', Object.keys(router))
On the project side, I am registering it in a server pluginI can see that: ๐Ÿ“‹ Registering router for module: todos ๐Ÿ“‹ Registered 6 schemas for module: todos ๐Ÿ”ง Built combined router with modules: todos But on actual requests I see: ๐Ÿš€ RPC Handler - Handling oRPC request: POST /rpc/todos/list ๐Ÿ”ง Built combined router with modules:
๐Ÿ”ง Available router modules: []
So somewhere I lost the registered items, I have tried with useRuntimConfig, same happens. What clears it between the server plugin and the request?
19 Replies
kapa.ai
kapa.aiโ€ข4w ago
Hi @BenG. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The issue you're encountering is due to the separation between build-time (when your Nuxt module and server plugins are registered) and runtime (when your API handlers are executed). In Nuxt 3/Nuxt 4, the server context is stateless between requests, and any in-memory state (like your routerRegistry and schemaRegistry maps) initialized at build or module registration time will not persist across requests in production or serverless environments. This is especially true when using Nitro, which can spin up new server instances per request (e.g., in serverless deployments) or reloads modules between requests for HMR in development. Key points from the documentation: - Runtime Context vs. Build Context:
The runtime context (where your API handlers run) is isolated from the build context (where modules and plugins are registered). They do not share state, code, or context except through runtime configuration or persistent storage. This means that any in-memory state set up in a server plugin or module will not be available in your event handler at request timeย Runtime Context vs. Build Context. - Server Plugins and Nitro Lifecycle:
Nitro plugins (in server/plugins/) are executed once when the server starts, but in serverless or development environments, the server (and thus the plugin) may be re-initialized per request, losing any in-memory stateย Nuxt Lifecycle: Nitro Plugins. - How to Share State:
To persist data between build-time/module registration and runtime/request handling, you must use a persistent mechanism, such as: - The runtime config (useRuntimeConfig), but this is meant for configuration, not dynamic state. - A cross-platform storage layer (e.g., Redis, file storage) via Nitro's storage APIย Server Storage. - Or, if you need to share code, export functions or data from a shared module and import them in both places, but not in-memory state. What is happening in your case: - You register routers in a server plugin (executed at server start/build time). - When a request comes in, your handler runs in a new context, and the in-memory registry is empty. How to fix: - Store your router and schema registry in a persistent storage (e.g., Nitro storage, file, or database), or - Register your routers at runtime (inside the handler or a Nitro plugin that runs per request), or - Use a static registry (i.e., export a constant or function from a shared file and import it in both the plugin and handler), but do not rely on in-memory mutation across build/runtime boundaries. Summary:
You cannot rely on in-memory state set up in server plugins or modules to be available in API handlers at request time. Use persistent storage or static code exports for sharing data between build-time and runtime in Nuxt/Nitroย Runtime Context vs. Build Context. If you need to register dynamic routers, consider using Nitro's storage layer or another persistent mechanism to store and retrieve your router definitionsย Server Storage. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiโ€ข4w ago
<@371347799255547916> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
BenG
BenGOPโ€ข4w ago
@kapa.ai Can you give me exampes for: Store your router and schema registry in a persistent storage (e.g., Nitro storage, file, or database), or Register your routers at runtime (inside the handler or a Nitro plugin that runs per request),
kapa.ai
kapa.aiโ€ข4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Certainly! Here are examples for both approaches, based strictly on the knowledge sources: --- 1. Store your router and schema registry in persistent storage (Nitro storage) You can use Nitro's storage layer to persist data between requests. For example, you can configure a storage driver (like filesystem or Redis) in your nuxt.config.ts:
export default defineNuxtConfig({
nitro: {
storage: {
routers: {
driver: 'fs',
base: './.data/routers'
},
schemas: {
driver: 'fs',
base: './.data/schemas'
}
}
}
})
export default defineNuxtConfig({
nitro: {
storage: {
routers: {
driver: 'fs',
base: './.data/routers'
},
schemas: {
driver: 'fs',
base: './.data/schemas'
}
}
}
})
See: Server Storage Example Then, in your server plugin or handler, you can use useStorage to set and get your routers and schemas:
// Register router
export default defineNitroPlugin(async () => {
const routersStorage = useStorage('routers')
await routersStorage.setItem('todos', yourRouterSlice)
const schemasStorage = useStorage('schemas')
await schemasStorage.setItem('todos', yourSchemas)
})

// In your handler
export default defineEventHandler(async (event) => {
const routersStorage = useStorage('routers')
const schemasStorage = useStorage('schemas')
const router = await routersStorage.getItem('todos')
const schemas = await schemasStorage.getItem('todos')
// ... use router and schemas
})
// Register router
export default defineNitroPlugin(async () => {
const routersStorage = useStorage('routers')
await routersStorage.setItem('todos', yourRouterSlice)
const schemasStorage = useStorage('schemas')
await schemasStorage.setItem('todos', yourSchemas)
})

// In your handler
export default defineEventHandler(async (event) => {
const routersStorage = useStorage('routers')
const schemasStorage = useStorage('schemas')
const router = await routersStorage.getItem('todos')
const schemas = await schemasStorage.getItem('todos')
// ... use router and schemas
})
See: Nitro Storage API --- 2. Register your routers at runtime (inside the handler or a Nitro plugin that runs per request) If your router definitions are static or can be imported, you can register them directly in your handler or a Nitro plugin:
// server/api/rpc/[...path].ts
import { todosRouter } from '~/server/routers/todos'
import { usersRouter } from '~/server/routers/users'

export default defineEventHandler(async (event) => {
const combinedRouter = {
todos: todosRouter,
users: usersRouter
}
// ... handle the request using combinedRouter
})
// server/api/rpc/[...path].ts
import { todosRouter } from '~/server/routers/todos'
import { usersRouter } from '~/server/routers/users'

export default defineEventHandler(async (event) => {
const combinedRouter = {
todos: todosRouter,
users: usersRouter
}
// ... handle the request using combinedRouter
})
This approach avoids the need for persistent storage by building the router object at runtime from static imports. --- Note:
- In-memory mutation (e.g., using a Map or object to register routers at build time) will not persist between requests in Nitro, especially in serverless or development environmentsย Nuxt Lifecycle: Nitro Plugins. - Use Nitro storage for dynamic, persistent data; use static imports for static router definitions. If you need more advanced dynamic registration, refer to the Nitro storage documentation for details on supported drivers and usage. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiโ€ข4w ago
<@371347799255547916> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
BenG
BenGOPโ€ข4w ago
@kapa.ai after which hooks the #import becomes available? I tried nitro:config, listen and modules:done, netiher worked
kapa.ai
kapa.aiโ€ข4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Based on the knowledge sources, the #imports alias (which provides access to auto-imported composables and utilities) becomes available after the auto-import system has been set up. According to the documented Nuxt build-time hooks, the relevant hooks for extending or interacting with imports are: - imports:sources - imports:extend - imports:context - imports:dirs These hooks are called at setup, allowing modules to extend sources, imports, and directories. The modules:done hook is called after installing user modules, but the auto-import system is set up before this point. Therefore, the #imports alias is not guaranteed to be available in nitro:config, listen, or modules:done. The documentation does not explicitly state the exact hook after which #imports becomes available for use, but it does indicate that the auto-import system is configured during the setup phase, specifically around the imports:* hooks. If you need to interact with or extend auto-imports, you should use these imports:* hooks rather than modules:done or later hooksย Nuxt Hooks (build time). I'm sorry, but the knowledge sources do not explicitly state the precise moment when the #imports alias becomes available for general use in your code. However, it is clear that it is set up during the imports-related hooks, before modules:done. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiโ€ข4w ago
<@371347799255547916> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
BenG
BenGOPโ€ข4w ago
what hook is called the latest, in general in nuxt? @kapa.ai
kapa.ai
kapa.aiโ€ข4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- The latest hook called in the general Nuxt lifecycle is the close hook. This hook is triggered when the Nuxt instance is gracefully closing, allowing you to perform cleanup tasks or shutdown logic. It is available both at build time and in the Nitro server lifecycleย Nuxt Hooks (build time)ย Lifecycle Hooks. Example usage:
export default defineNuxtConfig({
hooks: {
close: () => {
// Cleanup logic here
}
}
})
export default defineNuxtConfig({
hooks: {
close: () => {
// Cleanup logic here
}
}
})
This is the last documented hook in the Nuxt lifecycle according to the available knowledge sources. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiโ€ข4w ago
<@371347799255547916> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
BenG
BenGOPโ€ข4w ago
How to access scanDirs in the context of serverplugin: export default defineNitroPlugin((nitro) => {... @kapa.ai
kapa.ai
kapa.aiโ€ข4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I'm sorry, but the knowledge sources do not contain explicit information about accessing scanDirs in the context of a Nitro server plugin (i.e., within defineNitroPlugin). There is documentation on how to add scan directories using addServerScanDir in a Nuxt module setup, but not on how to access or read the current scan directories from within a Nitro pluginย addServerScanDir. If you need to modify or add scan directories, you should do so in a Nuxt module using the appropriate hooks or utility functions, not from within a Nitro plugin. The Nitro plugin context (nitro) does not appear to expose scanDirs based on the available documentation. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiโ€ข4w ago
<@371347799255547916> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
BenG
BenGOPโ€ข4w ago
@kapa.ai
kapa.ai
kapa.aiโ€ข4w ago
Let me check the knowledge base for you again. One moment! :books: Sorry @BenG, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
BenG
BenGOPโ€ข4w ago
@kapa.ai reach scanDirs in nuxt.hook('modules:done', async () => {
kapa.ai
kapa.aiโ€ข4w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I'm sorry, but the knowledge sources do not contain explicit information about how to access scanDirs within the nuxt.hook('modules:done', ...) context. The documentation describes how to add scan directories using addServerScanDir in a Nuxt module's setup function, but it does not mention a way to read or access the current list of scan directories from within the modules:done hook or any other hookย addServerScanDir. If you need to modify scan directories, you should use the provided utility functions during module setup. There is no documented method to "reach" or inspect scanDirs in the modules:done hook according to the available sources. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiโ€ข4w ago
<@371347799255547916> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?