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