Skip to content

deno-mage/server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mage Server

Build web applications with Deno and Preact

Installation

deno add jsr:@mage/server npm:preact

Getting started

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

Middleware

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
);

Available middleware

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

Context

A context object is passed to each middleware.

Url

The URL is parsed and placed on the context.

context.url.pathname
context.url.searchParams
...

Request

The request object is available on the context.

context.request.method
context.request.headers.get("Content-Type")
await context.request.text()
...

Response

The response object is available on the context.

context.response.headers.set("Content-Type", "text/plain");
context.response.headers.delete("Content-Type", "text/plain");

Response utilities

A number of utility methods are available to configure the response content.

text

Respond with text.

context.text(StatusCode.OK, "Hello, World!");

json

Respond with JSON.

context.json(StatusCode.OK, { message: "Hello, World!" });

render

Render JSX to HTML using Preact.

await context.render(
  StatusCode.OK,
  <html lang="en">
    <body>
      <h1>Hello, World!</h1>
    </body>
  </html>,
);

empty

Respond with an empty response, useful for response like 204 No Content.

context.empty(StatusCode.NoContent);

redirect

Redirect the request to another location.

context.redirect(RedirectType.Permanent, "/new-location");

rewrite

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");

serveFile

Serve a file from the file system.

await context.serveFile("path/to/file");

Cookies

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

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

Wildcards are parsed from the URL and placed on the context.

// /public/* -> /public/one/two/three

context.wildcard; // one/two/three

Routing

HTTP methods

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

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");
});

Parameters

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}`,
  );
});

Wildcards

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
 */

Running your app

To run your app, you can use the Deno.serve function:

Deno.serve(app.build());