Skip to content

Commit b4e9bc4

Browse files
authored
feat: update project metadata, enhance documentation, and add new middleware guides (#77)
1 parent e285ef2 commit b4e9bc4

8 files changed

Lines changed: 424 additions & 52 deletions

File tree

docs/.vitepress/config.mjs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,17 @@ export default defineConfig({
3131
{ text: 'Project Stucture', link: '/docs/guides/project-structure' },
3232
{ text: 'Routing', link: '/docs/guides/routing' },
3333
{ text: 'Request & Response', link: '/docs/guides/req-res' },
34-
{ text: 'Middlewares', link: '/docs/guides/middleware' }
34+
{
35+
text: 'Middleware',
36+
link: '/docs/guides/middleware',
37+
items: [
38+
{ text: 'Guard', link: '/docs/guides/middleware/guard' },
39+
{ text: 'Validator', link: '/docs/guides/middleware/validator' },
40+
{ text: 'Interceptor', link: '/docs/guides/middleware/interceptor' },
41+
42+
]
43+
},
44+
{ text: 'Exception', link: '/docs/guides/exception' },
3545
]
3646
},
3747
{

docs/docs/blog/changelog.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,47 @@
11
# Changelog
22

3+
## `v0.6.0` - June 2, 2026
4+
5+
***`Nova.chain()` has been removed from the public API. Route-specific middleware is now handled exclusively through Attributes (`--@Guard`, `--@Interceptor`, `--@Validator`).***
6+
7+
### Added
8+
9+
- Exception handling utility with common HTTP error responses (`Nova.exception`) (#76)
10+
- Request validation via `--@Validator` attribute with support for `body`, `param`, and `query` (#73)
11+
- Interceptor support via `--@Interceptor` attribute using onion architecture (#72)
12+
- Guard attribute now supports multiple rules — `--@Guard(Auth, Admin)` (#72)
13+
- Initial `--@Guard` attribute support with comment-based syntax (#69)
14+
15+
### Changed
16+
17+
- Startup logger now displays registered routes with their applied Attributes (#75)
18+
- Runtime request logs now include status code and applied Attributes, with color-coded output by status class (`2xx` green, `3xx` blue, `4xx`/`5xx` red) (#75)
19+
- Core architecture restructured and modularized; adapters renamed (#71)
20+
- `libs/` renamed to `adapters/` (#69)
21+
- `Nova.chain()` is no longer exposed publicly — route-specific middleware must now be defined using Attributes (#69)
22+
23+
### Fixed
24+
25+
- Correct status property access in `devMiddleware` (#74)
26+
27+
---
28+
29+
**Before**
30+
```luau
31+
Home.Get = Nova.chain({ AuthMiddleware, AdminMiddleware }, function()
32+
return Nova.response.send("Hello, World")
33+
end)
34+
```
35+
36+
**After**
37+
```luau
38+
--@Guard(AuthRule, AdminRule)
39+
--@Interceptor(TransformRule)
40+
function Home.Get()
41+
return Nova.response.send("Hello, World")
42+
end
43+
```
44+
345
## `v0.5.7-test` - May 4, 2026
446

547
### Changed

docs/docs/guides/exception.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Exception
2+
3+
Nova provides a built-in `Exception` module for throwing structured HTTP errors from anywhere in your application — route handlers, Guard rules, Interceptors, or Validators. Exceptions are caught by Nova's global error handler and converted into a proper HTTP response automatically.
4+
5+
## Usage
6+
7+
Luau does not have a `throw` keyword, but `error()` behaves similarly — it halts execution immediately and can be caught by `pcall`. Nova's global error handler uses `pcall` under the hood, so it detects when an Exception is thrown and responds with the appropriate status code and message.
8+
9+
```luau
10+
local Nova = require("@nova")
11+
local Exception = Nova.exception
12+
13+
local function Auth(req: Nova.Request)
14+
error(Exception.Forbidden())
15+
end
16+
17+
return Auth
18+
```
19+
20+
This reads almost exactly like `throw new ForbiddenException()` in other languages.
21+
22+
## Custom Messages
23+
24+
Every built-in Exception accepts an optional message. If omitted, a default message is used.
25+
26+
```luau
27+
-- Uses default message: "Forbidden"
28+
error(Exception.Forbidden())
29+
30+
-- Uses a custom message
31+
error(Exception.Forbidden("You do not have access to this resource"))
32+
```
33+
34+
## Built-in Exceptions
35+
36+
### 4xx — Client Errors
37+
38+
| Function | Status | Default Message |
39+
|---|---|---|
40+
| `Exception.BadRequest()` | `400` | Bad Request |
41+
| `Exception.Unauthorized()` | `401` | Unauthorized |
42+
| `Exception.Forbidden()` | `403` | Forbidden |
43+
| `Exception.NotFound()` | `404` | Not Found |
44+
| `Exception.Conflict()` | `409` | Conflict |
45+
| `Exception.TooManyRequests()` | `429` | Too Many Requests |
46+
47+
### 5xx — Server Errors
48+
49+
| Function | Status | Default Message |
50+
|---|---|---|
51+
| `Exception.InternalServerError()` | `500` | Internal Server Error |
52+
| `Exception.BadGateway()` | `502` | Bad Gateway |
53+
| `Exception.ServiceUnavailable()` | `503` | Service Unavailable |
54+
55+
## Custom Exceptions
56+
57+
If you need a status code not covered by the built-in functions, use `Exception.HttpException()` directly — it is the base function all others are built on:
58+
59+
```luau
60+
error(Exception.HttpException(418, "I'm a teapot"))
61+
```
62+
63+
The first argument is the status code and the second is the message. Both are required.
64+
65+
## Important Notes
66+
67+
- `error()` halts execution immediately. Nothing after it runs.
68+
- Exceptions are only meaningful when thrown inside Nova's request pipeline. Throwing one outside of a request context will not produce an HTTP response.
69+
- All built-in Exceptions accept an optional custom message. `HttpException` requires one.

docs/docs/guides/middleware.md

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,52 @@ Common use cases include:
77
- **Logging:** Tracking request times and paths.
88
- **Authentication:** Checking if a user is logged in.
99
- **Validation:** Ensuring the request body contains the correct data.
10-
- **Security:** Adding headers or blocking malicious IPs.
10+
- **Security:** Adding headers.
1111

1212
## The Middleware Function
1313

1414
A middleware in Nova is a simple function that receives two arguments:
1515

16-
- `req:` The Request object.
17-
- `next:` A function that, when called, passes control to the next middleware in the stack.
16+
- `req` The Request object.
17+
- `next` A function that, when called, passes control to the next middleware in the stack.
1818

1919
### The Onion Pattern
2020

21-
Nova uses the **Onion Pattern**. This means that when you call `next()`, the code execution **dives** into the next middleware or the final handler. Once the handler finishes, the execution "bubbles" back up, running the code after `next()`.
21+
Nova uses the **Onion Pattern**. This means that when you call `next()`, execution **dives** into the next middleware or the final handler. Once the handler finishes, execution "bubbles" back up, running the code after `next()`.
2222

2323
```luau
2424
local function myMiddleware(req, next)
2525
print("1. This runs BEFORE the route handler")
26-
27-
next() -- Move to the next middleware or handler
28-
26+
27+
next()
28+
2929
print("2. This runs AFTER the route handler is finished")
3030
end
3131
```
3232

33+
**Example Flow:**
34+
35+
If you have Global Middleware `A`, Attribute `B`, and Handler `C`:
36+
37+
```bash
38+
A (before next) → B (before next) → C (Handler) → B (after next) → A (after next)
39+
```
40+
3341
## Global Middleware
3442

35-
Global middlewares are executed for **every single request** made to your server. These are defined when you initialize your Nova application.
43+
Global middleware runs on **every single request** made to your server. These are defined when you initialize your Nova application.
3644

37-
The `Nova.new()` constructor accepts an optional second argument: a table of middleware functions.
45+
`Nova.new()` accepts an optional second argument: a table of middleware functions.
3846

3947
```luau
4048
local Nova = require("path/to/nova")
4149
4250
local app = Nova.new(8080, {
43-
function(req, next)
51+
myMiddleware1 = function(req, next)
4452
print(`[{req.method}] {req.path}`)
4553
next()
4654
end,
47-
function(req, next)
48-
-- Another global middleware
55+
myMiddleware1 = function(req, next)
4956
next()
5057
end
5158
})
@@ -55,57 +62,58 @@ app:listen(function()
5562
end)
5663
```
5764

58-
## Route-Specific Middleware (`Nova.chain`)
59-
60-
Sometimes you only want middleware to run on specific routes (e.g., protecting a `/dashboard` route). For this, Nova provides the `Nova.chain` utility.
65+
Global middlewares execute in the order they are defined.
6166

62-
`Nova.chain` takes two arguments:
67+
## Route-Specific Middleware (Attributes)
6368

64-
- A table of middlewares.
65-
- The final route handler function.
69+
For middleware that should only run on specific routes, Nova uses **Attributes** — a comment-based syntax inspired by how decorators work in other languages.
6670

67-
Usage in `route.luau`
71+
Attributes are defined directly above a route handler function using the `--@` prefix:
6872

6973
```luau
70-
local Nova = require("path/to/nova")
71-
7274
local Home = {}
7375
74-
-- A simple auth middleware
75-
local function checkAuth(req, next)
76-
if req.headers["Authorization"] then
77-
next()
78-
else
79-
-- If we don't call next(), the chain stops here
80-
return Nova.response.json({ error = "Unauthorized" }, { status = 401 })
81-
end
76+
--@Guard(Auth)
77+
--@Interceptor(Transform)
78+
function Home.Get()
79+
return Nova.response.send("Hello, World")
8280
end
8381
84-
-- Using Nova.chain to protect this specific GET route
85-
Home.Get = Nova.chain({ checkAuth }, function(req)
86-
return Nova.response.json({
87-
message = "Welcome to the protected dashboard!"
88-
})
89-
end)
90-
9182
return Home
9283
```
9384

94-
## Execution Order
85+
Nova ships with three built-in Attributes, each serving a distinct purpose:
9586

96-
It is important to understand the order in which Nova executes your code:
87+
| Attribute | Purpose |
88+
|---|---|
89+
| [`--@Guard`](/middleware/guard) | Protect routes — authentication and authorization |
90+
| [`--@Interceptor`](/middleware/interceptor) | Wrap the handler — logging, response transformation |
91+
| [`--@Validator`](/middleware/validator) | Validate incoming data — body, params, and query |
9792

98-
- **Global Middlewares:** Executed in the order they were defined in `Nova.new`.
99-
- **Route Middlewares:** Executed in the order they appear in the `Nova.chain` table.
100-
- **Route Handler:** The actual logic inside your `Home.Get` or similar function.
101-
- **The "Bubble Up":** The code after `next()` in all middlewares runs in reverse order.
93+
### Execution Order
10294

103-
**Example Flow:**
95+
Attributes always execute in this order, regardless of how they are defined:
96+
97+
```bash
98+
Guard → Validator → Interceptor → Route Handler
99+
```
100+
101+
Nova enforces this order at startup. If your Attributes are defined out of order, Nova will throw an error with a clear message telling you what to fix.
102+
103+
### Convention-Driven Resolution
104+
105+
Each Attribute references a **Rule** by name. Nova resolves Rules by convention from your `src/` directory:
106+
107+
| Attribute | Rule Directory |
108+
|---|---|
109+
| `--@Guard(...)` | `src/guards/` |
110+
| `--@Interceptor(...)` | `src/interceptors/` |
111+
| `--@Validator(...)` | `src/validators/` |
104112

105-
If you have Global Middleware `A`, Route Middleware `B`, and Handler` C`:
106-
`A (before next)``B (before next)``C (Handler)``B (after next)``A (after next)`
113+
See the dedicated pages for each Attribute to learn how to define Rules.
107114

108115
## Important Notes
109116

110-
- **Always call `next()`:** If you do not call `next()`, the request will "hang" and never reach the handler (unless you intentionally return a response early to stop the request).
111-
- **Order Matters:** If Middleware A depends on data added to the request by Middleware B, Middleware B must come first in the table.
117+
- **Always call `next()`** in global middleware. If you do not, the request will never reach the handler unless you are intentionally returning an early response.
118+
- **Global middleware runs first**, before any Attributes on the route.
119+
- **Attributes are route-specific** — they have no effect on routes that do not declare them.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Guard
2+
3+
A Guard protects a route by running an authorization check before the handler is reached. If the check passes, the request proceeds. If it fails, Nova halts the pipeline and responds immediately.
4+
5+
Guards are the first Attribute to execute in the pipeline:
6+
7+
```bash
8+
Guard → Validator → Interceptor → Route Handler
9+
```
10+
11+
## Defining a Guard Attribute
12+
13+
Apply a Guard to a route handler using the `--@Guard` comment above the function:
14+
15+
```luau
16+
local Home = {}
17+
18+
--@Guard(Auth)
19+
function Home.Get()
20+
return Nova.response.send("Hello, World")
21+
end
22+
23+
return Home
24+
```
25+
26+
Multiple rules can be passed to a single Guard:
27+
28+
```luau
29+
--@Guard(Auth, Admin)
30+
function Home.Get()
31+
return Nova.response.send("Hello, World")
32+
end
33+
```
34+
35+
Rules are executed left to right. If any rule fails, the pipeline halts.
36+
37+
## Defining a Guard Rule
38+
39+
Guard Rules live in `src/guards/`. Each file exports a single function that receives the request and returns a `boolean`.
40+
41+
```luau
42+
-- src/guards/Auth.luau
43+
local Nova = require("@nova")
44+
45+
local function Auth(req: Nova.Request)
46+
return true -- allow the request
47+
end
48+
49+
return Auth
50+
```
51+
52+
If the rule returns `false`, Nova responds with `401 Unauthorized` and the pipeline stops. If it returns `true`, the request moves to the next step.
53+
54+
## Throwing Exceptions
55+
56+
Returning `false` always produces a `401`. If you need a different status code, use `Nova.exception` with Luau's `error()` instead:
57+
58+
```luau
59+
local Nova = require("@nova")
60+
local Exception = Nova.exception
61+
62+
local function Auth(req: Nova.Request)
63+
error(Exception.Forbidden())
64+
end
65+
66+
return Auth
67+
```
68+
69+
`error()` halts execution immediately and Nova's global error handler catches it, responding with the appropriate status code and message.
70+
71+
You can also pass a custom message:
72+
73+
```luau
74+
error(Exception.Forbidden("You do not have access to this resource"))
75+
```
76+
77+
See the [Exception](/docs/guides/exception) page for all available exceptions.

0 commit comments

Comments
 (0)