SMALL BUT MIGHTY!
Yet another router? Why?! Well, the other ones are really big. They have dependencies and do too much. I just wanted a simple way to start up an HTTP server, handle some routes, and handle the core use cases that you need most of the time. 80-20 rule. No other fluff. And no dependencies!
In the 1.* versions of Minikin, it shipped with both the router and server together. With version 2.0 the base package of minikin
only includes the router. Choose the appropriate option below for your needs.
First install the project dependency:
npm i minikin
Now in our code, we will import Minikin and then instantiate an instance of Minikin Server. Finally we define a route that will wildcard respond to any request with "Hello from Minikin!". Notice that Server()
is awaited, so we wrap the entire thing in a self-calling async
function.
import Server from "minikin";
(async () => {
const minikin = await Server();
minikin.route("*", () => "Hello from Minikin!";
})();
In the above example, Minikin automatically picked an open port to listen on. The console log will tell you which port it picked or you can grab it with:
const port = minikin.port;
However, typically you want to specify the port to listen on. Just use the first argument to do so:
const minikin = await Server(8080);
Or of course you can use an environment variable:
const minikin = await Server(process.env.PORT);
If you are not going to use the web server part of Minikin, you can save a few bytes by instaling just the router component:
npm i minikin-router
We do not need to await the router, so it doesn't need to be wrapped in an await
function.
import Router from "minikin-router";
const minikin = Router();
If aren't using the Minikin server, it means you're listening for incoming requests yourself. So define the routes the same way the below examples show you. Then when you get the event for the incoming request, pass it to the Minikin router with the handle
method. It will parse it and return the response, which must be awaited.
const response = await minikin.handle(req);
The examples below for defining routes work the same for either the server or the router.
This will define a route that that response to GET
requests to the /yo
path. The second argument is a callback function that receives the incoming request. Here we simply return a string "Yo back at ya" as the response.
minikin.route("GET /yo", (req) => "Yo back at ya");
This is the equivalent of the more longhand:
minikin.route("GET /yo", (req) => text("Yo back at ya"));
You'll need to also import the text
class with:
import { text } from "minikin-router";
For the other similar response methods that you'll find below (like json
, file
, etc.), you'll need to similarly import the method like the above example.
All request callbacks also support async
so that you can await
other calls.
minikin.route("GET /some-text", async (req) => {
const text = await goGetSomeText();
return text(text);
});
To instead respond with JSON:
minikin.route("GET /some-json", async (req) => {
const data = await goGetSomeData();
return json(data);
});
Use the second argument of fromJson
, fromString
, and similar from*
methods to set additional parameters including the status code.
minikin.route("GET /error400", (req) =>
json(
{
error: `Don't call this endpoint!`,
},
{
status: 400,
}
)
);
You can also use that second argument to set headers:
minikin.route("GET /with-headers", (req) =>
text("This is the response body", {
headers: {
"X-Custom-Header": "Some Value",
},
})
);
Alternately you can set headers like this
minikin.route("GET /hello", () => text("Hey!").header("X-Foo", "Bar"));
If you want to add trailers, they work the same way as headers except are called trailer
minikin.route("GET /hello", () =>
text("Hi", {
trailers: { "X-Some-Trailer": "foobar" },
})
);
Or...
minikin.route("GET /bye", () =>
text("See ya later").trailer("X-Foo", "Bar")
);
Similarly you can set cookies on the response
minikin.route("GET /hello", () => text("Hey!").cookie("X-Foo", "Bar", 60));
The above sets the TTL (Max-Age) on the cookie to 60 seconds. Alternately, the third argument can accept an object allowing you to set any of the standard cookie options.
minikin.route("GET /hello", () =>
text("Hey!").cookie("X-Foo", "Bar", {
"Max-Age": 60,
SameSite: true,
})
);
Minikin can also handle URL path params out of the box:
minikin.route("GET /hello/:name", (req) =>
json({
message: `Hello to ${req.params.get("name")} from Minikin!`,
})
);
It will parse the JSON body automatically:
minikin.route("POST /person", (req) => {
const name = req.json.name; // { name: "Jason" }
return json({
message: `You want to create a new person named ${name}`,
});
});
If you want to do HTTPS, pass in your certificate information as the segment argument
const minikin = await Server(8000, {
pfx: fs.readFileSync("test/fixtures/test_cert.pfx"),
passphrase: "sample",
});
For a catch-all for all GET
requests, put this LAST:
minikin.route("GET *", () =>
json(
{
message: "File not found",
},
{ status: 404 }
)
);
If you want to catch all HTTP methods, use just the wildcard. Remember: ALWAYS put the catch-all case LAST!
minikin.route("*", () => text("Catch all"));
This is equivalent to
minikin.route("* *", () => text("Catch all"));
If you want to be even more concise, you can totally skip the first argument. This will be the same as the previous two examples.
minikin.route(() => text("Catch all"));
You can also wildcard parts of the route like:
minikin.route("GET /*/foo", () =>
json({
message: "This will respond to /hello/foo or /goodbye/foo",
})
);
And you can use regex within your routes. This will accept either /hello
or /hello/
. The question mark makes the trailing slash optional.
minikin.route("GET /hello/?", () =>
json({
message: "This will respond to /hello or /hello/",
})
);
If you need to handle multiple HTTP Methods with the same handler
minikin.route("PUT|PATCH /hello", () => text("Hi"));
You can also do a wildcard method
minikin.route("* /goodbye", () => text("Cya"));
If you do not set a method at all, it will be wildcard by default, which is equivalent to the last example.
minikin.route("/goodbye", () => text("Cya"));
Minikin will automatically read incoming cookies and make then available:
minikin.route("GET /hello", (req) =>
text(req.cookies.has("myCookie") ? "cookie set" : "cookie not set")
);
Minikin also reads the incoming headers for you:
minikin.route("GET /hello", (req) =>
text(req.headers.has("someHeader") ? "header set" : "header not set")
);
If it's a file that contains text:
minikin.route("GET /hello", () => file("/path/to/file"));
If it's a local binary file, like an image:
minikin.route("GET /hello", () => binary("/path/to/image"));
You can pull a resposne from a static file, like you would in the previous two examples, but then do simple template replacement. This example will replace any strings with {{ name }}
in the hello.html
file with the corresponding value in the second argument.
minikin.route("GET /hello", () =>
template("public/hello.html", { name: "Jason" })
);
Alternately, you can call the render method on any response to pass in the key-value replacement.
minikin.route("GET /hello/:name", () =>
text("Hello, {{ name }}").render({ name: req.params.get("name") })
);
Minkin also supports middleware, most often used as guards. This allows you to chain callbacks inline on a specific route. This first example adds authentication to a single route.
const requireAuthentication = (req: Request) => {
if (!req.headers.has("Authorization")) {
return text("Must Authenticate", { status: 401 });
}
};
minikin.route("GET /protected", requireAuthentication, () => text("OK"));
You can add global level middleware as well, which can act as plugins and pre-processors. The use
method allows this, similar to other frameworks. There is an alias for this called before
, which describes better what it does (runs before the normal routes). However, use
is the standard with other frameworks and so more familiar to developers.
minikin.use(parseJwtToken);
minikin.use(processFormEncodedInput);
minikin.use((req) => {
if (req.headers.get("User-Agent").test(/roku|firetv|appletv/i)) {
req.isConnectedTv = true;
}
});
These use
handlers are exactly the same as any other route. They just are processed first. So this means you can return a response from, which will stop any further processing.
minikin.use(() => text("STOP!"));
You cal also pass a first argument to restrict it to certain methods and paths. This can allow it to act as a guard for many endpoints at once:
minikin.use("PUT|POST|PATCH|DELETE /api/*", requireAuthentication);
minikin.use("/images/*", preventLeeching);
You can also list out multiple use callbacks, like you can on a normal route. This is useful to enforce multiple rules or to run the request through multiple pre-processors.
minikin.use("/admin/*", requireAuthentication, mustBeAdmin);
You can also use the routes
method (instead of singular route
method) to set multiple routes in an object form:
minikin.routes({
"GET /hello": () => "hello",
"GET /hello/:name": (req) => json({
message: `Hello to ${req.params.get('name')} from Minikin!`,
},
"GET /admin": () => text("Forbidden", {
status: 403
}
});
To do a basic redirect:
minikin.route("GET /foo", () => redirect("/bar"));
The default status code for a redirect is 302, but you can change that with the second argument:
minikin.route("GET /foo", () => redirect("/bar", 301));
On any route, you can call the after
method to run logic on the corresponding response. This allows you to modify the response before it gets sent back to the user. This first example would override the response of "hi" with "bye" instead:
router.route("GET /hello", () => text("hi")).after((res) => res.content("bye"));
If you want to run a modifier on the response of all endpoints (not a specific route), you can do so with the after
method.
router.after((res) => {
res.header("X-Some-Header", "value");
});
Just like you can with use
/before
and route
, you can add in a path as the first argument to after
to only run it on matching paths.
router.after("GET /api/*", (res) => {
// Allow CORS on API endpoints
res.header("Access-Control-Allow-Origin", "*");
});
And potentially, you could completely replace a response by returning it.
router.after("GET /replaceMe", (res) => text("some new response"));