deno add jsr:@mage/server npm:preact
Minimum TypeScript compiler options:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
An example app:
import { MageApp, StatusCode } from "@mage/server";
const app = new MageApp();
app.get("/", (context) => {
context.text(StatusCode.OK, "Hello, World!");
});
Deno.serve(app.build());
Run the app:
deno run --allow-all main.tsx
APIs are composed of stacked middleware. A simple middleware looks like this:
app.get("/", async (context, next) => {
console.log("Request received");
await next();
});
If you want to complete handling a request you simply don't call the next middleware:
app.get("/", (context) => {
context.text(StatusCode.OK, "Hello, World!");
});
You can register middleware to execute on every route and method via the
app.use
method. This is useful for middleware that should run on every
request.
app.use(async (context, next) => {
console.log("Request received");
await next();
});
You can configure multiple middleware at a time:
app.get(
"/",
(context) => {
context.text(StatusCode.OK, "One!");
},
(context) => {
context.text(StatusCode.OK, "Two!");
},
(context) => {
context.text(StatusCode.OK, "Three!");
},
// ... etc
);
A collection of prebuilt middleware is available to use.
useCors |
Configure CORS request handling |
useMethodNotAllowed |
Responds with 405, ignores preflight (OPTIONS) requests |
useNotFound |
Responds with 404, ignores preflight (OPTIONS) requests |
useOptions |
Responds to preflight (OPTIONS) requests |
useSecurityHeaders |
Adds recommended security headers to the response |
useServeFiles |
Serve files from a durectory based on the wildcard on context |
A context object is passed to each middleware.
The URL is parsed and placed on the context.
context.url.pathname
context.url.searchParams
...
The request object is available on the context.
context.request.method
context.request.headers.get("Content-Type")
await context.request.text()
...
The response object is available on the context.
context.response.headers.set("Content-Type", "text/plain");
context.response.headers.delete("Content-Type", "text/plain");
A number of utility methods are available to configure the response content.
Respond with text.
context.text(StatusCode.OK, "Hello, World!");
Respond with JSON.
context.json(StatusCode.OK, { message: "Hello, World!" });
Render JSX to HTML using Preact.
await context.render(
StatusCode.OK,
<html lang="en">
<body>
<h1>Hello, World!</h1>
</body>
</html>,
);
Respond with an empty response, useful for response like 204 No Content
.
context.empty(StatusCode.NoContent);
Redirect the request to another location.
context.redirect(RedirectType.Permanent, "/new-location");
You can rewrite requests to another location. This works for local and external URLs.
NOTE: This is not optimal for local rewrites, as it will make a new request to the provided location. This is useful for proxying requests to another server.
await context.rewrite("/new-location");
await context.rewrite("https://example.com");
Serve a file from the file system.
await context.serveFile("path/to/file");
You can read cookies from the request.
context.cookies.get("name");
You can set and delete cookies on the response.
context.cookies.set("name", "value");
context.cookies.delete("name");
Parameters are parsed from the URL and placed on the context.
// /user/:id/post/:postId -> /user/1/post/2
context.params.id; // 1
context.params.postId; // 2
Wildcards are parsed from the URL and placed on the context.
// /public/* -> /public/one/two/three
context.wildcard; // one/two/three
Routes can be registered for each HTTP method against a route:
app.get("/", (context) => {
context.text(StatusCode.OK, "Hello, World!");
});
// ... post, delete, put, patch, options, head, all
You can also register a route for all HTTP methods:
app.all("/", (context) => {
context.text(StatusCode.OK, "Hello, World!");
});
You can exclude the route and just register middleware against a HTTP method:
app.options((context) => {
console.log("Custom OPTIONS handler");
});
Paths can be simple:
app.get("/one", (context) => {
context.text(StatusCode.OK, "Simple path");
});
app.get("/one/two", (context) => {
context.text(StatusCode.OK, "Simple path");
});
app.get("/one/two/three", (context) => {
context.text(StatusCode.OK, "Simple path");
});
Paths can contain parameters that will be available on context.params.<name>
as strings.:
app.get("/user/:id", (context) => {
context.text(StatusCode.OK, `User ID: ${context.params.id}`);
});
app.get("/user/:id/post/:postId", (context) => {
context.text(
StatusCode.OK,
`User ID: ${context.params.id}, Post ID: ${context.params.postId}`,
);
});
Paths can contain wildcards that will match any path. Wildcards must be at the end of the path.
app.get("/public/*", (context) => {
context.text(StatusCode.OK, "Wildcard path");
});
The path portion captured by the wildcard is available on context.wildcard
.
app.get("/public/*", (context) => {
context.text(StatusCode.OK, `Wildcard path: ${context.wildcard}`);
});
Wildcards are inclusive of the path its placed on. This means that the wildcard will match any path that starts with the wildcard path.
app.get("/public/*", (context) => {
context.text(StatusCode.OK, "Wildcard path");
});
/**
* matches:
*
* /public
* /public/one
* /public/one/two
*/
To run your app, you can use the Deno.serve
function:
Deno.serve(app.build());