diff --git a/README.md b/README.md index b87884905a..12088fcfa0 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,9 @@ mkdir nitro-app cd nitro-app ``` -1️⃣ Create `api/hello.ts`: +1️⃣ Create `routes/index.ts`: -```ts [api/hello.ts] +```ts [routes/index.ts] export default () => 'nitro is amazing!' ``` @@ -63,7 +63,7 @@ export default () => 'nitro is amazing!' npx nitropack dev ``` -🪄 Your API is ready at http://localhost:3000/api/hello +🪄 Your API is ready at http://localhost:3000/ **🤓 [TIP]** Check `.nitro/dev/index.mjs` if want to know what is happening @@ -92,14 +92,16 @@ you should add the following to your `tsconfig.json` file: } ``` -## API Routes +## Routes and API Routes -API files inside `api/` directory will be automatically mapped to API routes and served using [h3](https://github.com/unjs/h3) router. +Handler files inside `routes/` and `api/` directory will be automatically mapped to [unjs/h3](https://github.com/unjs/h3) routes. + +**Note:** `api/` is a shortcut for `routes/api` as a common prefix. However please note that some deployment providers use `app/` directory for their API format. In this case, you can simply use `routes/api` or `srcDir` option to move everything under `src/` or `server/` directory. **Example:** Simple API route ```js -// api/test.ts +// routes/test.ts import { eventHandler } from 'h3' export default eventHandler(() => 'Hello World!') @@ -108,13 +110,12 @@ export default eventHandler(() => 'Hello World!') **Example:** API route with params ```js -// api/hello/[name].ts +// routes/hello/[name].ts import { eventHandler } from 'h3' -export default eventHandler(event => `Hello ${event.params.name}!`) +export default eventHandler(event => `Hello ${event.context.params.name}!`) ``` - ## Storage nitro provides a built-in storage layer using [unjs/unstorage](https://github.com/unjs/unstorage) that can abstract filesystem access. @@ -165,7 +166,7 @@ import { cachedEventHandler } from '#nitro' **Example:** Cache an API handler ```js -// api/test.ts +// routes/cached.ts import { defineCachedFunction } from '#nitro' const myFn = cachedEventHandler(async () => { @@ -187,7 +188,7 @@ const myFn = defineCachedFunction(async () => { ``` -**Example:** Enable cache on group of routes +**Example:** Enable cache on group of routes (**🧪 Experimental!**) ```js // nitro.config.ts @@ -404,7 +405,7 @@ Server's main base URL. Server handlers and routes. -If `api/` and `middleware/` directories exist, they will be automatically added to the handlers array. +If `routes/`, `api/` or `middleware/` directories exist, they will be automatically added to the handlers array. #### `devHandlers` @@ -416,6 +417,8 @@ We can use `devHandlers` but note that they are **only available in development #### `routes` +**🧪 Experimental!** + Route options. It is a map from route pattern (following [unjs/radix3](https://github.com/unjs/radix3)) to options. Example: diff --git a/examples/api-routes/api/hello/:name.ts b/examples/api-routes/api/hello/:name.ts index 1467b0cf5c..d016ff819f 100644 --- a/examples/api-routes/api/hello/:name.ts +++ b/examples/api-routes/api/hello/:name.ts @@ -1,3 +1,3 @@ import { defineEventHandler } from 'h3' -export default defineEventHandler(event => `Hello ${event.params.name}!`) +export default defineEventHandler(event => `Hello ${event.context.params.name}!`) diff --git a/examples/api-routes/public/index.html b/examples/api-routes/public/index.html deleted file mode 100644 index ad4ba117e6..0000000000 --- a/examples/api-routes/public/index.html +++ /dev/null @@ -1,2 +0,0 @@ -/api/hello
-/api/hello/world
diff --git a/examples/api-routes/routes/index.ts b/examples/api-routes/routes/index.ts new file mode 100644 index 0000000000..f50e23662b --- /dev/null +++ b/examples/api-routes/routes/index.ts @@ -0,0 +1,11 @@ +import { defineEventHandler } from 'h3' + +export default defineEventHandler(() => { + return ` +

API Routes:

+ +` +}) diff --git a/examples/cached-handler/api/test.ts b/examples/cached-handler/routes/index.ts similarity index 100% rename from examples/cached-handler/api/test.ts rename to examples/cached-handler/routes/index.ts diff --git a/examples/hello-world/api/hello.ts b/examples/hello-world/api/hello.ts deleted file mode 100644 index 3cf01ed22a..0000000000 --- a/examples/hello-world/api/hello.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { defineEventHandler } from 'h3' - -export default defineEventHandler(() => 'Nitro is amazing!') diff --git a/examples/hello-world/public/index.html b/examples/hello-world/public/index.html deleted file mode 100644 index 1dac32ac5b..0000000000 --- a/examples/hello-world/public/index.html +++ /dev/null @@ -1 +0,0 @@ -/api/hello diff --git a/examples/hello-world/routes/index.ts b/examples/hello-world/routes/index.ts new file mode 100644 index 0000000000..9d10a339f7 --- /dev/null +++ b/examples/hello-world/routes/index.ts @@ -0,0 +1,3 @@ +import { defineEventHandler } from 'h3' + +export default defineEventHandler(() => '

nitro is amazing!

') diff --git a/examples/middleware/middleware/test.ts b/examples/middleware/middleware/auth.ts similarity index 54% rename from examples/middleware/middleware/test.ts rename to examples/middleware/middleware/auth.ts index 268a96ef2c..473b8236bc 100644 --- a/examples/middleware/middleware/test.ts +++ b/examples/middleware/middleware/auth.ts @@ -1,5 +1,5 @@ import { defineEventHandler } from 'h3' export default defineEventHandler((event) => { - console.log('Middleware:', event.req.url) + event.context.auth = { name: 'User ' + Math.round(Math.random() * 100) } }) diff --git a/examples/middleware/routes/index.ts b/examples/middleware/routes/index.ts new file mode 100644 index 0000000000..bb881d80cf --- /dev/null +++ b/examples/middleware/routes/index.ts @@ -0,0 +1,5 @@ +import { defineEventHandler } from 'h3' + +export default defineEventHandler(event => ({ + auth: event.context.auth +})) diff --git a/playground/app.ts b/examples/plugins/routes/index.ts similarity index 100% rename from playground/app.ts rename to examples/plugins/routes/index.ts diff --git a/package.json b/package.json index 51e8df1837..6bd0ecc508 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "fs-extra": "^10.0.1", "globby": "^13.1.1", "gzip-size": "^7.0.0", - "h3": "^0.6.0", + "h3": "^0.7.1", "hasha": "^5.2.2", "hookable": "^5.1.1", "http-proxy": "^1.18.1", diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts index a581aebb35..14ed31af57 100644 --- a/playground/nitro.config.ts +++ b/playground/nitro.config.ts @@ -1,5 +1,4 @@ import { defineNitroConfig } from '../src' export default defineNitroConfig({ - renderer: '~/app' }) diff --git a/playground/routes/index.ts b/playground/routes/index.ts new file mode 100644 index 0000000000..1cc398b615 --- /dev/null +++ b/playground/routes/index.ts @@ -0,0 +1,3 @@ +import { eventHandler } from 'h3' + +export default eventHandler(() => '

Hello Nitro!

') diff --git a/src/rollup/config.ts b/src/rollup/config.ts index 3a7cdd9e4e..a2b3f8499c 100644 --- a/src/rollup/config.ts +++ b/src/rollup/config.ts @@ -193,7 +193,7 @@ export const getRollupConfig = (nitro: Nitro) => { ...nitro.options.handlers ] if (nitro.options.serveStatic) { - handlers.unshift({ route: '/', handler: '#nitro/static' }) + handlers.unshift({ route: '', handler: '#nitro/static' }) } if (nitro.options.renderer) { handlers.push({ route: '/**', handler: nitro.options.renderer }) diff --git a/src/rollup/plugins/handlers.ts b/src/rollup/plugins/handlers.ts index 9eeac59fc3..ca442939c6 100644 --- a/src/rollup/plugins/handlers.ts +++ b/src/rollup/plugins/handlers.ts @@ -40,7 +40,7 @@ ${imports.map(handler => `import ${getImportId(handler)} from '${handler}';`).jo ${lazyImports.map(handler => `const ${getImportId(handler)} = () => import('${handler}');`).join('\n')} export const handlers = [ -${handler.map(m => ` { route: '${m.route || '/'}', handler: ${getImportId(m.handler)}, lazy: ${m.lazy || true} }`).join(',\n')} +${handler.map(m => ` { route: '${m.route || ''}', handler: ${getImportId(m.handler)}, lazy: ${m.lazy || true} }`).join(',\n')} ]; `.trim() // console.log(code) diff --git a/src/runtime/app.ts b/src/runtime/app.ts index 628c29f413..6283642f18 100644 --- a/src/runtime/app.ts +++ b/src/runtime/app.ts @@ -45,7 +45,7 @@ function createNitroApp (): NitroApp { }) } - if (h.route === '/') { + if (h.route === '') { h3App.use(config.app.baseURL, handler) } else { router.use(h.route, handler) diff --git a/src/scan.ts b/src/scan.ts index 163ad9e2e5..a40edb3393 100644 --- a/src/scan.ts +++ b/src/scan.ts @@ -1,15 +1,16 @@ import { resolve, join } from 'pathe' import { globby } from 'globby' -import { withBase } from 'ufo' +import { withBase, withLeadingSlash, withoutTrailingSlash } from 'ufo' import type { Nitro, NitroEventHandler } from './types' export const GLOB_SCAN_PATTERN = '**/*.{ts,mjs,js,cjs}' -type FileInfo = { dir: string, name: string, path: string } +type FileInfo = { dir: string, path: string, fullPath: string } export async function scanHandlers (nitro: Nitro) { const handlers = await Promise.all([ scanMiddleware(nitro), - scanAPI(nitro) + scanRoutes(nitro, 'api', '/api'), + scanRoutes(nitro, 'routes', '/') ]).then(r => r.flat()) nitro.scannedHandlers = handlers.flatMap(h => h.handlers) @@ -19,16 +20,24 @@ export async function scanHandlers (nitro: Nitro) { export function scanMiddleware (nitro: Nitro) { return scanServerDir(nitro, 'middleware', file => ({ - route: '/', - handler: file.path + route: '', + handler: file.fullPath })) } -export function scanAPI (nitro: Nitro) { - return scanServerDir(nitro, 'api', file => ({ - handler: file.path, - route: withBase(file.name.replace(/\[([a-z]+)\]/g, ':$1'), '/api') - })) +export function scanRoutes (nitro: Nitro, dir: string, prefix: string = '/') { + return scanServerDir(nitro, dir, (file) => { + let route = file.path + .replace(/\.[a-z]+$/, '') + .replace(/index$/, '') + .replace(/\[([a-z]+)\]/g, ':$1') + route = withLeadingSlash(withoutTrailingSlash(withBase(route, prefix))) + + return { + handler: file.fullPath, + route + } + }) } async function scanServerDir (nitro: Nitro, name: string, mapper: (file: FileInfo) => NitroEventHandler) { @@ -44,10 +53,8 @@ function scanDirs (dirs: string[]): Promise { return fileNames.map((fileName) => { return { dir, - name: fileName - .replace(/\.[a-z]+$/, '') - .replace(/\/index$/, ''), - path: resolve(dir, fileName) + path: fileName, + fullPath: resolve(dir, fileName) } }).sort((a, b) => b.path.localeCompare(a.path)) })).then(r => r.flat()) diff --git a/test/utils.ts b/test/utils.ts index 09414ea74d..49dd58c2ee 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -37,8 +37,8 @@ export async function setupTest (preset) { }) await prepare(nitro) await copyPublicAssets(nitro) - await build(nitro) await prerender(nitro) + await build(nitro) afterAll(async () => { if (ctx.server) { diff --git a/yarn.lock b/yarn.lock index ec542aecdf..9141db1eee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4103,15 +4103,15 @@ __metadata: languageName: node linkType: hard -"h3@npm:^0.6.0": - version: 0.6.0 - resolution: "h3@npm:0.6.0" +"h3@npm:^0.7.1": + version: 0.7.1 + resolution: "h3@npm:0.7.1" dependencies: cookie-es: ^0.5.0 destr: ^1.1.0 radix3: ^0.1.1 ufo: ^0.7.11 - checksum: be187ff3a60b2a9a63babb9ea5d824d121c854396229d8843b9ac39dba316c27dbbd9fe30e111ac59dd5f6e826a98934d1e2e92d3b89cdb78b5c8d999ac89772 + checksum: 72845e5c77fb4974a2ed50d036c5a8277b5362c59f83a7f8dc8ab36e5fe4952e6134d2aaca6eab7a7baa146a3cd3e8a26ac062ce9761eb9135a968ca765244a7 languageName: node linkType: hard @@ -5692,7 +5692,7 @@ __metadata: fs-extra: ^10.0.1 globby: ^13.1.1 gzip-size: ^7.0.0 - h3: ^0.6.0 + h3: ^0.7.1 hasha: ^5.2.2 hookable: ^5.1.1 http-proxy: ^1.18.1