Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nested router - doesnt work #13696

Closed
mihail727 opened this issue Apr 10, 2022 · 14 comments
Closed

nested router - doesnt work #13696

mihail727 opened this issue Apr 10, 2022 · 14 comments

Comments

@mihail727
Copy link

mihail727 commented Apr 10, 2022

Environment


  • Operating System: Linux
  • Node Version: v16.14.2
  • Nuxt Version: 3.0.0-27460146.2ad93eb
  • Package Manager: npm@7.17.0
  • Builder: vite
  • User Config: -
  • Runtime Modules: -
  • Build Modules: -

Reproduction

https://stackblitz.com/edit/github-zvowq4-gj7iuh?file=pages/index.vue

Describe the bug

with latest version of nuxt 3 and follow the documentation i cant create nested route
when i'm trying to get data from api i get error:

handle is not a function
  at eval (file://./node_modules/h3/dist/index.mjs:57:24)  
  at new Promise (<anonymous>)  
  at callHandle (file://./node_modules/h3/dist/index.mjs:54:10)  
  at eval (file://./node_modules/h3/dist/index.mjs:50:12)  
  at eval (file://./node_modules/h3/dist/index.mjs:78:34)  
  at async handle (file://./node_modules/h3/dist/index.mjs:318:19)  
  at async ufetch (file://./node_modules/unenv/runtime/fetch/index.mjs:22:17)  
  at async $fetchRaw2 (file://./node_modules/ohmyfetch/dist/chunks/fetch.mjs:144:20)  
  at async setup (file://./.nuxt/dist/server/server.mjs:2442:176)
[Vue Router warn]: No match found for location with path "/test"

Additional context

No response

Logs

No response

@Aareksio
Copy link
Contributor

Aareksio commented Apr 10, 2022

The error with handle is not a function is resolved on latest release. Your reproduction have h3@0.4.2, over 20 days old release. With clear install and h3@7.2.0 it is fixed. However, the nested router does not work:

[nitro] [request error] Invalid lazy handler result. It should be a function:
  at eval (./node_modules/h3/dist/index.mjs:349:17)  
  at async eval (./node_modules/h3/dist/index.mjs:470:19)  
  at async Server.nodeHandler (./node_modules/h3/dist/index.mjs:420:7)

Updating data.ts to export router.handler solves the issue, but we can observe the following:

  • GET /api/data/test is not handled by the handler at all
  • GET /api/data is handled with req.url === '/api/data'

As there does not seem to be a check for whether the handler is a router, it is effectively useless, as the only route it can handle is it's own file path. There is a logic in h3 to adjust req.url on nested layers, but it does not seem to apply for this case.

@danielroe
Copy link
Member

For now, you can workaround this with:

import { createRouter, useBase } from 'h3';

const router = createRouter();

router.get('/', () => 'Hello World');

export default useBase('/api/test', router.handler);

@mihail727
Copy link
Author

For now, you can workaround this with:

import { createRouter, useBase } from 'h3';

const router = createRouter();

router.get('/', () => 'Hello World');

export default useBase('/api/test', router.handler);

but how to work with another route?
for example one more router.post(...)

@ahku
Copy link

ahku commented Apr 19, 2022

For now, you can workaround this with:

import { createRouter, useBase } from 'h3';

const router = createRouter();

router.get('/', () => 'Hello World');

export default useBase('/api/test', router.handler);

From the useBase naming I would assume that you could to nested routes, like router.get('/data', ...) instead of router.get('/', ...) to enable the route /api/test/data but I can't seem to get that to work.

Would be nice to be able to create all your routes programmatically rather than using the file-based routes.

@Aareksio
Copy link
Contributor

Aareksio commented Apr 19, 2022

@ahku As of right now you can not. The handler is working only for the file-system path no matter if you use useBase or not. Currently defining programmatic routes is not possible this way. The only mean I am aware of to programmatically define routes is by using server middleware and handling req / res yourself.

// @/server/middleware/api.ts -- arbitrary file name inside /server/middleware

import { createRouter, defineEventHandler, useBase } from 'h3';

const router = createRouter();
router.get('/hello', defineEventHandler(event => 'Hello'));
router.get('/hello/world', defineEventHandler(event => 'Hello World'));
const handler = useBase('/api', router.handler);

export default (req, res, next) => {
  if (!req.originalUrl.startsWith('/api')) {
    return next();
  }

  return handler(req, res);
}

Edit, scratch that.
@danielroe code is perfectly fine, but you must use catch-all:

// @/server/api/[...].ts -- catch-all route

import { createRouter, defineEventHandler, useBase } from 'h3';

const router = createRouter();
router.get('/hello', defineEventHandler(event => 'Hello'));
router.get('/hello/world', defineEventHandler(event => 'Hello World'));

export default useBase('/api', router.handler);

Edit 2, you can not use root (/) path in nested router when using catch-all ([...].ts) 😅

@mjrobinson86
Copy link

Quick question, is it possible to hook this workaround up with the nuxt/kit addServerMiddleware function? Ideally I want to delegate a route to a h3 router handler. Might be missing something obvious but I havn't had much luck so far.

I assume that you would pass addServerMiddleware the route, e.g. 'api/test', then the value of handler would be useBase(, router.handler) as demonstrated above instead of simple just the router itself as I would think the original intent was. I'm not sure how to pass in a catch all route programatically like this.

@Aareksio
Copy link
Contributor

Aareksio commented Apr 26, 2022

@mjrobinson86 Here's an example with express instead of h3 router, but the principle applies. addServerMiddleware should accept the same parameters as nuxt.config.serverMiddleware. https://stackblitz.com/edit/github-5bzsuf?file=nuxt.config.ts

@mjrobinson86
Copy link

@Aareksio thank you! I was missing the full path in useBase as the first argument, I had assumed that passing the route in the call to addServerMiddleware would have been sufficient then using simply useBase('/', router.handler) would have been sufficient.

This pointed me in the right direction. :)

@aligzl
Copy link

aligzl commented May 1, 2022

@Aareksio your solution works during development atm. When trying to deploy on hosting i got fallowing error.

Error: Something went wrong with the request to Cloudflare... Uncaught TypeError: Cannot read properties of undefined (reading 'name') at line 1 at line 1 [API code: 10021]

@Aareksio
Copy link
Contributor

Aareksio commented May 2, 2022

@aligzl This is likely a separate problem specific to CF. I suggest creating reproduction and raising another issue.

@nopeless
Copy link
Contributor

nopeless commented Nov 26, 2022

When will this be fixed?

https://nuxt.com/docs/guide/directory-structure/server#using-a-nested-router

It seems to still be an example on the Nuxt website

Also, we should be able to omit useBase(<current file path>, router.handler) as it is inferred by the file structure

@danielroe danielroe added the 3.x label Jan 19, 2023
@danielroe danielroe transferred this issue from nuxt/framework Jan 19, 2023
@obust
Copy link

obust commented Jan 31, 2023

the following does register the route /api/users/:id but not /api/users

// @/server/api/users/[...].ts

import { createRouter, defineEventHandler, useBase } from 'h3';

const router = createRouter();
router.get('', defineEventHandler(event => 'all users'));
router.get('/:id', defineEventHandler(event => 'one user'));

export default useBase('/api/users', router.handler);

could it be done like this since useBase return an EventHandler anyway ?:

// @/server/api/users.ts

import { createRouter, defineEventHandler, useBase } from 'h3';

const router = createRouter();
router.get('', defineEventHandler(event => 'all users'));
router.get('/:id', defineEventHandler(event => 'one user'));

export default useBase('/api/users', router.handler);

@Zebnastien
Copy link

Same as @obust.

What would be the recommended Nuxt way to handle /api/users and /api/users/[id] in the same file ?

@danielroe
Copy link
Member

Let's track in #18571.

@danielroe danielroe closed this as not planned Won't fix, can't repro, duplicate, stale Aug 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants