You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/docs/blog/changelog.md
+42Lines changed: 42 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,47 @@
1
1
# Changelog
2
2
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)
- 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)
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.
Copy file name to clipboardExpand all lines: docs/docs/guides/middleware.md
+55-47Lines changed: 55 additions & 47 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,45 +7,52 @@ Common use cases include:
7
7
-**Logging:** Tracking request times and paths.
8
8
-**Authentication:** Checking if a user is logged in.
9
9
-**Validation:** Ensuring the request body contains the correct data.
10
-
-**Security:** Adding headers or blocking malicious IPs.
10
+
-**Security:** Adding headers.
11
11
12
12
## The Middleware Function
13
13
14
14
A middleware in Nova is a simple function that receives two arguments:
15
15
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.
18
18
19
19
### The Onion Pattern
20
20
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()`.
22
22
23
23
```luau
24
24
local function myMiddleware(req, next)
25
25
print("1. This runs BEFORE the route handler")
26
-
27
-
next() -- Move to the next middleware or handler
28
-
26
+
27
+
next()
28
+
29
29
print("2. This runs AFTER the route handler is finished")
30
30
end
31
31
```
32
32
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
+
33
41
## Global Middleware
34
42
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.
36
44
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.
38
46
39
47
```luau
40
48
local Nova = require("path/to/nova")
41
49
42
50
local app = Nova.new(8080, {
43
-
function(req, next)
51
+
myMiddleware1 = function(req, next)
44
52
print(`[{req.method}] {req.path}`)
45
53
next()
46
54
end,
47
-
function(req, next)
48
-
-- Another global middleware
55
+
myMiddleware1 = function(req, next)
49
56
next()
50
57
end
51
58
})
@@ -55,57 +62,58 @@ app:listen(function()
55
62
end)
56
63
```
57
64
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.
61
66
62
-
`Nova.chain` takes two arguments:
67
+
## Route-Specific Middleware (Attributes)
63
68
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.
66
70
67
-
Usage in `route.luau`
71
+
Attributes are defined directly above a route handler function using the `--@` prefix:
Nova ships with three built-in Attributes, each serving a distinct purpose:
95
86
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 |
97
92
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
102
94
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/`|
104
112
105
-
If you have Global Middleware `A`, Route Middleware `B`, and Handler` C`:
See the dedicated pages for each Attribute to learn how to define Rules.
107
114
108
115
## Important Notes
109
116
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.
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