A sensible wrapper around µWebSockets.js for fast yet logical REST and WebSocket APIs
  • JavaScript 100%
Find a file
2026-02-09 03:15:13 +01:00
errorchains Initial commit 2026-02-09 03:15:13 +01:00
lib Initial commit 2026-02-09 03:15:13 +01:00
middlewares Initial commit 2026-02-09 03:15:13 +01:00
validators Initial commit 2026-02-09 03:15:13 +01:00
.gitignore Initial commit 2026-02-09 03:15:13 +01:00
index.js Initial commit 2026-02-09 03:15:13 +01:00
package-lock.json Initial commit 2026-02-09 03:15:13 +01:00
package.json Initial commit 2026-02-09 03:15:13 +01:00
quaesitor.png Initial commit 2026-02-09 03:15:13 +01:00
README.md Initial commit 2026-02-09 03:15:13 +01:00

quaesitor

Quaesitor is a routing framework for µWebSockets.js, to build structured REST and WebSocket APIs without sacrificing on µWebSockets.js's performance.

Quaesitor is extracted from a professional server component I wrote. The proprietary code has been removed to let this program stand independently and work for my personal projects without licensing issues.

Extremely Quick Start

npm i git+https://patchii.net/ikagi/quaesitor.git#v2.0.1

import Quaesitor from 'quaesitor';

const app = Quaesitor.App({});
const quaesitor = Quaesitor.Router(app);

quaesitor.get('/', [ // Route for GET /
	// Normal execution flow here
	Quaesitor.Middlewares.GetIP(), // Obtain client's IP in req.ip
	(res, req) => {
		res.finish(Buffer.from(`Hi ${ req.ip }!`, 'utf-8')); // Respond with a message
	}
], [
	// Execution flow in case of an error here
	Quaesitor.ErrorChains.PrintAllErrors() // Display all exceptions
]);

app.listen(28422, (token) => {
	if (token) {
		console.log('Listening on port 28422');
	} else {
		console.error('Failed to listen! Is the port in use?');
	}
});

Quaesitor aims to do one thing: give you an easy to program server for REST, file serving and WebSockets, while keeping the speed of µWebSockets. The main tradeoff is that routes are independent. A request is handled by exactly one route which is responsible for the entire request lifecycle, unlike hierarchical routers like ExpressJS, which allow multiple nested routes to handle a request.

To support composition of a request's lifecycle, Quaesitor lets you assign every route two chains: the handler chain and the exception chain. The request will first travel down the handler chain in order, waiting for any async handlers as needed. If an exception is thrown, execution flow is sent to the exception chain, where you may then choose to handle the request in a different way (respond with an error page, log the error, etc.).

For ease of use, Quaesitor comes with some nice-to-haves:

  • Built-in JSON parsing and responses
    • Read in a JSON body through the handler Quaesitor.Middlewares.JSONBody.
    • Return a JSON body by using the handler Quaesitor.Middlewares.JSONResponse. (This will wrap req.finish to take an object instead of a buffer)
    • Return a JSON error automatically using the error handler Quaesitor.ErrorChains.JSONErrors (by throwing Quaesitor.Errors.DisplayableError)
  • Built-in validation and reading of body, query and params data
    • Read data with Quaesitor.Middlewares.JSONBody, Quaesitor.Middlewares.MultipartBody, Quaesitor.Middlewares.Params or Quaesitor.Middlewares.Query
    • Pass an object to them with keys to validate, where every key is an array of Validators
  • Header parsing and sending
  • Combining multiple routers into one (Slipstream routers)
  • Safer WebSockets (µWebSockets will terminate your process in case of misuse)
  • Automatic corking and streaming of large objects

Important Gotchas

  • I cannot guarantee provide support for this software. My day job leaves me mostly wanting to play squid-shaped shooters on my downtime.
  • This software assumes you're OK with a bit of duck typing and "open-thinking" code. Most notably, handlers may come in multiple designs (function, object, function that creates an object) and it's on you to not mind this and see it as the pro I personally believe it is.
  • µWebSockets is strict and will only leave buffers in memory until its callback is completed. This means that as soon as you enter any async code, all code that needs to read request data is dead. Be careful with this, as while I have protected some code paths from misuse, µWebSockets will happily give you out of bounds reads. Keep this in mind!

Overview

Also see the µWebSockets documentation, as it will undoubtably come in handy.

Creating an Application

Quaesitor wraps around µWebSockets in the literal sense. Quaesitor comes with its own µWebSockets, but you can bring your own if you believe they'll be compatible. Quaesitor.App and Quaesitor.SSLApp are equal to µWebSockets.App and µWebSockets.SSLApp respectively. To start using Quaesitor, pass it your µWebSockets App:

import Quaesitor from 'quaesitor';
const app = Quaesitor.App({}); // Passing options here is only necessary for SSLApp.
const quaesitor = Quaesitor.Router(app);
const port = 28422;

app.listen(port, (token) => { // You can also pass hostname, port, callback
	if (token) {
		console.log(`Started server on port ${ port }`);
	} else {
		console.error(`Could not start server on port ${ port }`);
	}
});

Creating Routes

Quaesitor allows you to listen to the following methods: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE. You can create a route by using any of those nine utility functions on your Router. For example, quaesitor.get(pattern, handlerChain, exceptionChain):

quaesitor.get('/', [
	Quaesitor.Middlewares.GetIP(),
	(res, req) => {
		res.finish(Buffer.from(`Hi ${ req.ip }!`, 'utf-8'));
	}
], [
	Quaesitor.ErrorChains.PrintAllErrors() // use DisplayableErrors in production instead
]);

This simple route will handle requests to /, and respond with the user's IP address. To unpack this code:

  • quaesitor.get creates a GET route. It takes the pattern (here just /), and the two chains.
  • The pattern we're using is /, which will match only that path. You may also use named parameters (e.g. /users/:userid/:relationshipid will match /users/12345/abcde), or wildcards (e.g. /users/* will match /users/beans/somewhere/else).
  • The handler chain contains two handlers:
    • Quaesitor.Middlewares.GetIP() returns a handler which attaches the user's IP address as a string to req.ip. req and res are shared across all handlers, so one handler amending it will propagate down the chain.
    • Our own handler below it calls res.finish, which sends the given buffer and ends the request afterwards. (If a request isn't marked as finished, then you'll get an error page.)
  • The error chain only contains one handler:
    • Quaesitor.ErrorChains.PrintAllErrors() returns a handler that will show all errors with full stack traces in an error page. So don't use it in production.

Taking in data

Getting the user's IP is neat but not particularly useful. You want to let the user take actions in your application! And unfortunately there's a lot of ways in HTTP to send data to your server. From the top, they're headers, path parameters, the query string, and the body. Let's do the first three first:

quaesitor.get('/beans/:beanid', [
	Quaesitor.Middlewares.Headers(['User-Agent']),
	Quaesitor.Middlewares.Params({
		beanid: [Quaesitor.V.IsString({ minlength: 2, maxlength: 12 })]
	}),
	Quaesitor.Middlewares.Query({
		count: [Quaesitor.V.Optional(), Quaesitor.V.IsInt()]
	}),
	(res, req) => {
		const userAgent = req.headers.get('User-Agent');
		const beanID = req.params.beanid;
		const beanCount = req.query.count ?? 'an unknown amount of';
		res.finish(Buffer.from(`Hi ${ userAgent }, you just requested ${ beanCount } beans with bean ID ${ beanID }!`, 'utf-8'));
	}
], [
	Quaesitor.ErrorChains.PrintAllErrors()
]);

If you now open http://127.0.0.1:28422/beans/abc?count=100, you will see something along the lines of Hi Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0, you just requested 100 beans with bean ID abc! But if you leave out the ?count, then it'll say an unknown amount of beans. But if you try to pass ?count=wowie then it complains. Similarly, if you make the abc too long or short, then it complains as well. You've just learned Quaesitor validation, which makes handling user data straightforward. Let's step through the example code:

  • The Quaesitor.Middlewares.Headers handler simply requests extra headers. It just returns { headers: ['User-Agent'], handler: ()=>{} }, which is the "expanded" handler syntax (an object) that you shouldn't need to write unless you're making your own middlewares. You can't read a header unless it's been requested before! The headers will become available in req.headers. By the way, headers are case-insensitive. Both in the middleware and in req.headers. So if you prefer writing them lowercase that's fine too.
  • The Quaesitor.Middlewares.Params handler will read path parameters out of the path, that is, the :beanid. All the keys in the object you pass must exist in your pattern (here /beans/:beanid). For each key you add, you supply an array of validators.
    • The Quaesitor.V.IsString validator ensures the value is set and is a string. Path parameters are strings, so this won't break on the path /beans/123. But it also allows us to set a maximum and minimum length.
    • The params will end up as an object in req.params.
  • The Quaesitor.Middlewares.Query handler will read the query string (anything after the ? in the path). It works the same as Params.
    • The Quaesitor.V.Optional validator will shortcut if the key is not set (is undefined). If so, validation is halted for this key. If the key is set, validation will proceed as normal.
    • The Quaesitor.V.IsInt validator ensures the value is set and is an integer. If the value is a string, then it will be converted unless you supply the strict option.
    • The query params will end up as an object in req.query.
  • By the way, Quaesitor.V is just an alias for Quaesitor.Validation.Validators. Because that'd get annoying to type. (No, I don't mind if you alias Quaesitor to QTR or something.)

Loading body data is pretty easy too. Let's look at a raw body first:

quaesitor.post('/echo', [
	Quaesitor.Middlewares.Body(),
	(res, req) => {
		res.finish(req.body);
	}
], [
	Quaesitor.ErrorChains.PrintAllErrors()
]);

This simply echoes our body buffer right back at us. Quaesitor.Middlewares.Body simply makes the request's body available in req.body. By the way, in case you haven't noticed, we're using POST now. If you're certain you won't do any async code between the moment you consume req.body and the start of the request, you can pass Quaesitor.Middlewares.Body(true), which disables it. This opens you up to a use-after-free! µWebSockets will usually detach the buffer it gives, but no guarantees. But less copying means more fast, so think ahead.

Parsing JSON bodies is just as easy:

quaesitor.post('/register', [
	Quaesitor.Middlewares.JSONBody({
		username: [Quaesitor.V.Matches({ pattern: /^[a-z0-9]{3,16}$/i }, 'Your username may only consist of numbers and letters')],
		email: [Quaesitor.V.IsEmail()],
	}),
	(res, req) => {
		res.finish(Buffer.from(`Hi ${ req.body.username } with email ${ req.body.email }!`, 'utf-8'));
	}
], [
	Quaesitor.ErrorChains.PrintAllErrors()
]);

Quaesitor.Middlewares.JSONBody loads in the body as JSON, places it into req.body as an object, and will validate it just as we've seen before. Some new validators we haven't seen yet:

  • Quaesitor.V.Matches takes a RegEx pattern pattern and checks it. Here, we're ensuring a username of only letters and numbers, with at least three and at most sixteen characters. We also supply a different error message, by setting the second argument to a string. (If you're not passing any options, you can pass the error message as the first argument.)
  • Quaesitor.V.IsEmail checks whether the input looks like an email. But the best check is always trying to send an email for real, so this is just a sanity check and to ensure it's a string that could at least look like an email.

One thing to note: body validation currently doesn't support nesting. It's something I want to add in the future, where you can use either Quaesitor.V.IsObject or just put an object directly in order to also check nested fields. For now, please use your own ability to do so.

Finally, you can also use multipart bodies (for file uploads):

quaesitor.post('/echoFile', [
	Quaesitor.Middlewares.LimitBodySize(2*1024*1024),
	Quaesitor.Middlewares.MultipartBody({
		file: [Quaesitor.V.IsFile()],
	}),
	(res, req) => {
		res.headers.set('Content-Type', req.body.file.type);
		res.finish(req.body.file.buffer);
	}
], [
	Quaesitor.ErrorChains.PrintAllErrors()
]);

Once again, we're echoing a file back to ourselves. Quaesitor.Middlewares.MultipartBody reads in files as well as any normal multipart fields, and the validator Quaesitor.V.IsFile allows us to ensure it's a file. Files will then end up in req.body as an object with the fields filename, type and buffer. You can use Quaesitor.Middlewares.LimitBodySize to limit the size of the body (that includes everything, not just the file itself). This is a bit faster because it allows to potentially reject the request early. But if you want to do it for the file specifically, or you want to decide it per file, Quaesitor.V.IsFile has minsize and maxsize options. You're also seeing here how to set response headers. It's simple, just use the res.headers Map!

WebSockets

In general, Quaesitor only implements the HTTP side of WebSocket functionality. Once you've obtained one, it's up to you to read the µWebSockets documentation. I won't show a full WebSocket implementation here. But to get you going:

quaesitor.ws('/ws', {
	idleTimeout: 120,
	maxPayloadLength: 16 * 1024 * 1024,
	// any other settings you'd like, see https://unetworking.github.io/uWebSockets.js/generated/interfaces/WebSocketBehavior.html
}, [
	(res, req) => {
		res.upgrade();
	}
], [
	Quaesitor.ErrorChains.PrintAllErrors()
], (res, req, ws) => {
	console.log('New connection!');
	ws.send('Hi! I will now echo everything you send', false);

	ws.onmessage = (message, isBinary) => {
		console.log(message);
		ws.send(message, isBinary);
	};

	ws.onclose = (code, message) => {
		console.log('Connection closed!');
	};
});

The quaesitor.ws method will create a WebSocket route. The second argument contains options for the WebSocket's behaviour, like the maximum size of a payload. The handler chain will be able to use res.upgrade(), which you can call to upgrade the request to a WebSocket. If a WebSocket is established, the function at the end will be called, which gives you res and req again, but also a new ws, which is a thinly wrapped µWebSockets WebSocket.

Serving static content

You'll often enough want to serve some static files. You can serve either a single file, or a folder structure.

quaesitor.get('/file.png', [
	Quaesitor.Middlewares.ServeStaticFile('./file.png'),
	Quaesitor.Middlewares.ServeStaticFile('./404.png')
], [
	Quaesitor.ErrorChains.PrintAllErrors()
]);

Quaesitor.Middlewares.ServeStaticFile will serve the file given, or fall through. By default it has some options set, we'll look at what options can be used in the next example. So in this case, it will first try to serve file.png, then if that fails, 404.png, and if that fails, then we fall out of the handler and an error page is shown.

Let's serve a folder next:

quaesitor.get('/*', [
	Quaesitor.Middlewares.ServeStaticFolder('./public', '/', {
		cache: {
			duration: 100
		}
	}),
	(res) => { Quaesitor.Utils.GenerateErrorPage(res, '404 Not Found'); }
], [
	Quaesitor.ErrorChains.PrintAllErrors()
]);

Quaesitor.Middlewares.ServeStaticFolder serves the folder and any deeper folders. It also automatically will serve an index.html if a folder is requested. The first argument is the root of the folder to serve, the second argument is the root of the request path to attach to. As in, if you are serving /* this should be /, if you are serving /files/* it should be /files/, etc. This is so the handler knows what to mask off of the request path. By default, this will also exclude any dotfiles (files starting with a .). You can set the option hideDotfiles to false if you prefer.

JSON responses

Quaesitor includes a system where you can implement an "API responder", which will replace res.finish with a wrapper. The one included with Quaesitor is Quaesitor.Middlewares.JSONResponse. Once you use it, instead of using res.finish with a Buffer, you use it with a JSON object, and you'll be sending back that object (plus success: true). On the errors side, you can use Quaesitor.ErrorChains.JSONErrors.

quaesitor.get('/stats', [
	Quaesitor.Middlewares.JSONResponse(),
	(res, req) => {
		res.finish({ count: 123 });
	}
], [
	Quaesitor.ErrorChains.JSONErrors()
]);

As a response, you'll get { "success": true, "count": 123 }. I'm sure you can imagine that combining this with JSONBody, you'll have a very easy time developing REST APIs!

Keeping it organized

If you're daunted by the fact that every route needs to redeclare its entire route, have you considered that JS allows you to spread arrays into other arrays? If you have a collection of handlers you use multiple times, you can bundle them into one array:

const APIStack = [
	Quaesitor.Middlewares.ResponseHeaders({
		'Cache-Control': 'public, max-age=0',
		'Referrer-Policy': 'strict-origin-when-cross-origin',
		'X-Content-Type-Options': 'nosniff',
		'X-DNS-Prefetch-Control': 'off',
		'X-Download-Options': 'noopen',
		'X-Frame-Options': 'SAMEORIGIN',
		'X-XSS-Protection': '1; mode=block',
	}),
	Quaesitor.Middlewares.JSONResponse(),
	Quaesitor.Middlewares.LimitBodySize(32*16*1024),
];

quaesitor.get('/', [
	...APIStack, // Now you can use them as one!
	(res, req) => {
		res.finish(Buffer.from('Hi!', 'utf-8'));
	}
], []);

Slipstreaming

If you're developing in multiple files, it'll get annoying fast to have to pass your Router to everywhere you want to add routes. How about doing the opposite? You can create a SlipstreamRouter like so:

const quaesitor = Quaesitor.SlipstreamRouter();

quaesitor.get('/stats', [
	Quaesitor.Middlewares.JSONResponse(),
	(res, req) => {
		res.finish({ count: 123 });
	}
], [
	Quaesitor.ErrorChains.JSONErrors()
]);

Then you can merge this SlipstreamRouter back into your real Router like so:

realQuaesitor.slipstream(quaesitor);

This adds all the routes you've staged in your SlipstreamRouter into your original Router. This way, you can define routes in separate files, and all those files need to do is export the SlipstreamRouter.

API reference

Quaesitor.App(options)

Quaesitor.SSLApp(options)

Equal to µWebSockets.App and µWebSockets.SSLApp. Returns a µWebSockets.TemplatedApp.

µWebSockets.TemplatedApp.listen(port, cb)

µWebSockets.TemplatedApp.listen(host, port, cb)

µWebSockets.TemplatedApp.listen(port, options, cb)

See µWebSockets.TemplatedApp.listen. Listens on the given port and optional host.

  • number port: The port number to listen on.
  • string host: The host name to listen on.
  • µWebSockets.ListenOptions options: Controls listen behaviour mode.

Quaesitor.Router(app)

Returns a Router. Wraps around the given App.

Router.connect(pattern, chain, exceptionChain)

Router.delete(pattern, chain, exceptionChain)

Router.get(pattern, chain, exceptionChain)

Router.head(pattern, chain, exceptionChain)

Router.options(pattern, chain, exceptionChain)

Router.patch(pattern, chain, exceptionChain)

Router.post(pattern, chain, exceptionChain)

Router.put(pattern, chain, exceptionChain)

Router.trace(pattern, chain, exceptionChain)

Router.getOnly(pattern, chain, exceptionChain)

Router.any(pattern, chain, exceptionChain)

Add a route to the given method and pattern, and attach the given chain and exception chain to said route.

  • string pattern: The path to attach this route to.
    • You can use wildcards (*) and parameter names (:name).
  • array chain: The chain of handlers. A handler may be either:
    • a function, which will be executed,
    • an object with the following fields:
      • function handler: The function to be executed,
      • boolean requiresPath: Whether this handler needs to know the current path. If any handler needs it, req.path will be set. Optional.
      • array headers: An array of strings of the header names needed by this handler. req.headers will be set with all headers requested by all handlers. Optional.
    • The function to be executed will be given the arguments res, req, err:
      • Response res: The response object.
        • µWebSockets.HttpResponse raw: The raw µWebSockets response object.
        • boolean aborted: Whether the request was aborted. This may be set if the user disconnects early.
        • boolean finished: Whether the request was finished. If by the end of the handler chain the request isn't finished, an error page will be shown.
        • string? status: The status to return (both the number and the name), or null if 200 OK is fine.
        • Map<string, string> headers: The response headers to send.
        • function finish: The finisher function. By default, takes a Buffer and sends it, and marks the request as finished.
        • function _finish: Always the default finisher function.
        • boolean nobody: If true, the method is HEAD and no response body should be sent.
      • Request req: The request object.
        • µWebSockets.HttpRequest raw: The raw µWebSockets request object.
        • string method: The request method (i.e. GET).
        • string pattern: The pattern of this route.
        • Quaesitor.Utils.CaseInsensitiveMap<string, string> headers: The request headers. This map is case-insensitive.
        • object query: The query parameters. Empty if not hydrated by Quaesitor.Middlewares.Query.
        • object params: The path parameters. Empty if not hydrated by Quaesitor.Middlewares.Params.
        • mixed body: The body of the request. Hydrated by multiple different middlewares in different ways.
        • string path: The path of the request. Hydrated only if one or more handlers requested it.
      • Error err: If in the exception chain, the caught exception.
    • If a handler explicitly returns false, the chain will be aborted early. If this causes the chain to end without finished being true, this will still cause an error page to be sent.
  • array exceptionChain: The chain of handlers to jump to in case of an exception in the normal chain. Same contents as above.

Special notes:

  • Router.get will create a handler both for GET and for HEAD as per spec. If you must make a handler for only GET, use Router.getOnly.
  • Router.any will create a handler for all methods.

Router.ws(pattern, wsOptions, chain, exceptionChain, wsHandler)

Add a route to the given method and pattern, and attach the given chain and exception chain to said route. Allow the route to upgrade to a WebSocket connection using the given wsOptions, and if a connection is established, calls wsHandler.

  • string pattern: The path to attach this route to. See above.
  • µWebSockets.WebSocketBehavior wsOptions: Options for the behaviour of the WebSocket. Do not set upgrade, open, message, drain or close, they are redefined by Quaesitor.
  • array chain: The chain of handlers. See above.
    • Your chain will be able to use res.upgrade(), which will upgrade to a WebSocket.
  • array exceptionChain: The chain of handlers to jump to in case of an exception in the normal chain. See above.
  • function wsHandler: Called when the connection is established. Will be called with res, req, ws:
    • Response res: The Response, same as within a chain.
    • Response req: The Request, same as within a chain.
    • µWebSockets.WebSocket ws: The established WebSocket. Note that Quaesitor adds some utility hooks:
      • function onmessage: Override this to be notified of messages. You'll receive message, isBinary:
        • ArrayBuffer message: The message sent.
        • boolean isBinary: Whether the message is binary. (You'll get an ArrayBuffer either way, though.)
      • function ondrain: Override this to be notified when the messages were drained from your stack. No arguments.
      • function onclose: Override this to be notified when the WebSocket is closed. You'll receive code, message:
        • number code: The close code.
        • ArrayBuffer message: Any close message sent.
      • function removeAllListeners: Call this to remove the three above listeners.

Router.slipstream(slipstreamRouter)

Takes all the routes defined in the SlipstreamRouter, and adds them into the Router.

  • Quaesitor.SlipstreamRouter slipstreamRouter: The SlipstreamRouter to take routes from.

Quaesitor.SlipstreamRouter()

Returns a SlipstreamRouter, which can be used to attach routes to as you would a Router. They can then be exported and loaded into a real Router at once.

SlipstreamRouter.connect(pattern, chain, exceptionChain)

SlipstreamRouter.delete(pattern, chain, exceptionChain)

SlipstreamRouter.get(pattern, chain, exceptionChain)

SlipstreamRouter.head(pattern, chain, exceptionChain)

SlipstreamRouter.options(pattern, chain, exceptionChain)

SlipstreamRouter.patch(pattern, chain, exceptionChain)

SlipstreamRouter.post(pattern, chain, exceptionChain)

SlipstreamRouter.put(pattern, chain, exceptionChain)

SlipstreamRouter.trace(pattern, chain, exceptionChain)

SlipstreamRouter.getOnly(pattern, chain, exceptionChain)

SlipstreamRouter.any(pattern, chain, exceptionChain)

SlipstreamRouter.ws(pattern, chain, exceptionChain)

Same behaviour as the matching Router methods.

SlipstreamRouter.pushRoutes(router)

Takes all the routes defined in this SlipstreamRouter, and adds them into the given Router. The opposite of Router.slipstream.

  • Quaesitor.Router router: The Router to push routes into.

Quaesitor.Errors.QuaesitorUsageError extends Error

An Error thrown when your usage of Quaesitor is incorrect.

Quaesitor.Errors.DisplayableError extends Error

An Error that the user should be able to see.

new Quaesitor.Errors.DisplayableError(message, newStatus)

Construct a new DisplayableError.

  • string message: The error message.
  • string newStatus: The status code. Optional, default is 422 Unprocessable Entity.

Quaesitor.Errors.ValidationError extends Quaesitor.Errors.DisplayableError

An Error that the user should be able to see, notifying them of an error in their input.

new Quaesitor.Errors.ValidationError(message, key, context)

Construct a new ValidationError.

  • string message: The error message.
  • string key: The key whose value is incorrect.
  • string context: The location this key can be found in (query, params, body, etc.).

Quaesitor.Middlewares.JSONResponse()

Returns a handler that sets the response header Content-Type to application/json and replaces res.finish with one that takes an object.

Quaesitor.Middlewares.Body(unsafe)

Returns a handler that reads the body of the request and surfaces it as a Buffer at req.body.

  • boolean unsafe: If true, will skip memory copying. WARNING: This is memory-unsafe if the body is then read after any async operation. µWebSockets will have freed the Buffer. (Optional, default false)

Quaesitor.Middlewares.JSONBody(elements)

Returns a handler that reads the body of the request as JSON, validates it, and surfaces it as an object at req.body.

  • object elements: The keys to validate. For each key, an array of validators.

Quaesitor.Middlewares.MultipartBody(elements, unsafe)

Returns a handler that reads the body of the request as Multipart, validates it, and surfaces it as an object at req.body.

  • object elements: The keys to validate. For each key, an array of validators.
  • boolean unsafe: If true, will skip memory copying. WARNING: This is memory-unsafe if any files' buffers are then read after any async operation. µWebSockets will have freed the Buffer. (Optional, default false)

Quaesitor.Middlewares.LimitBodySize(maxSize)

Returns a handler that will generate an error page and stop early (not jump to the exception chain) if the payload was too large.

  • number maxSize: The maximum size of the body in bytes.

Quaesitor.Middlewares.GetIP()

Returns a handler that will add the user's IP address as a string at req.ip.

Quaesitor.Middlewares.Headers(keys)

Returns a handler that will request the headers given by keys to be added in req.headers.

  • array keys: An array of strings of each header to request.

Quaesitor.Middlewares.Query(elements)

Returns a handler that extracts the request's query parameters, validates them, and surfaces them as an object at req.query.

  • object elements: The keys to validate. For each key, an array of validators.

Quaesitor.Middlewares.Params(elements)

Returns a handler that extracts the request's path parameters, validates them, and surfaces them as an object at req.params.

  • object elements: The keys to validate. For each key, an array of validators.

Quaesitor.Middlewares.ResponseHeaders(headers)

Returns a handler that sends additional response headers.

  • object headers: The headers to respond with, as a key/value object.

Quaesitor.Middlewares.Redirect(to, isPermanent)

Returns a handler that sends a redirect, then cuts execution short and finishes.

  • string to: The target of the redirect.
  • boolean isPermanent: If true, sends a 301. Otherwise, sends a 302. (Optional, default false)

Quaesitor.Middlewares.ServeStaticFile(filePath, options)

Returns a handler that serves a single file. Falls through if the file was not found or could not be sent.

  • string filePath: The path of the file to serve.
  • object options: Additional options:
    • boolean etag: If true, attach ETags, and read ETags sent by the client. This allows client-side caching. (Default true)
    • number etag_maximum: The maximum file size for hash-based ETags, in bytes. Files larger than this will use mtime-based ETags. (Default 10MiB)
    • object cache: Options for caching. (If not supplied, caching is disabled.)
      • boolean enabled: Whether caching is enabled. (Default true)
      • number duration: How long to keep a file cached, in milliseconds. (Default 5000)
      • Map store: The cache store to use. By default, use the one built into Quaesitor. If you use your own, you will need to evict cache yourself.
      • boolean attachHeaders: Whether to add the informational header X-Quaesitor-Cache about the cache status to the response. (Default true)

Quaesitor.Middlewares.ServeStaticFolder(root, mask, options)

Returns a handler that serves an entire folder structure. Falls through if the file was not found or could not be sent.

  • string root: The path of the folder to serve.
  • string mask: The path of the route to ignore. For example, if you are serving at /files/*, then this should be /files/ in most cases.
  • object options: Additional options:
    • boolean hideDotfiles: If true, files starting with a dot (.) will be ignored and fall through. (Default true)
    • string serveIndex: If not null, when a directory is requested, will try to serve this file from said directory. (Default index.html)
    • boolean etag: If true, attach ETags, and read ETags sent by the client. This allows client-side caching. (Default true)
    • number etag_maximum: The maximum file size for hash-based ETags, in bytes. Files larger than this will use mtime-based ETags. (Default 10MiB)
    • object cache: Options for caching. (If not supplied, caching is disabled.)
      • boolean enabled: Whether caching is enabled. (Default true)
      • number duration: How long to keep a file cached, in milliseconds. (Default 5000)
      • Map store: The cache store to use. By default, use the one built into Quaesitor. If you use your own, you will need to evict cache yourself.
      • boolean attachHeaders: Whether to add the informational header X-Quaesitor-Cache about the cache status to the response. (Default true)

Quaesitor.V.Exists(msg)

Quaesitor.V.Exists(options, msg)

Returns a Validator that throws if the value is undefined.

  • object options: No options for this validator.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsSet(msg)

Quaesitor.V.IsSet(options, msg)

Returns a Validator that throws if the value is undefined or an empty string.

  • object options: No options for this validator.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsIn(options, msg)

Returns a Validator that throws if the value is not seen in the array given by options.values.

  • object options:
    • array values: The allowed values.
  • string msg: If set, will return this message instead.

Quaesitor.V.Optional()

Returns a Validator that returns a ValidationStop if the value is undefined. A ValidationStop will stop any further checks on the value.

Quaesitor.V.IsNonEmptyString(msg)

Quaesitor.V.IsNonEmptyString(options, msg)

Returns a Validator that throws if the value is not a string, or contains no visible characters.

  • object options: No options for this validator.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsMongoID(msg)

Quaesitor.V.IsMongoID(options, msg)

Returns a Validator that throws if the value is not a string, or is not 24 hexadecimal characters.

  • object options: No options for this validator.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsUpperCase(msg)

Quaesitor.V.IsUpperCase(options, msg)

Returns a Validator that throws if the value is not a string, or is not uppercase.

  • object options:
    • boolean strict: If false, strings will be forced to uppercase instead of throwing. Default false.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsLowerCase(msg)

Quaesitor.V.IsLowerCase(options, msg)

Returns a Validator that throws if the value is not a string, or is not lowercase.

  • object options:
    • boolean strict: If false, strings will be forced to lowercase instead of throwing. Default false.
  • string msg: If set, will return this message instead.

Quaesitor.V.Matches(options, msg)

Returns a Validator that throws if the value is not a string, or does not match the pattern given by options.pattern.

  • object options:
    • RegExp pattern: Required. The RegEx pattern to test against.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsEmail(msg)

Quaesitor.V.IsEmail(options, msg)

Returns a Validator that throws if the value is not a string, or is not shaped like an email address.

  • object options: No options for this validator.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsString(msg)

Quaesitor.V.IsString(options, msg)

Returns a Validator that throws if the value is not a string.

  • object options:
    • number minlength: If given, strings shorter than this will be rejected.
    • number maxlength: If given, strings longer than this will be rejected.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsInt(msg)

Quaesitor.V.IsInt(options, msg)

Returns a Validator that throws if the value is not an integer.

  • object options:
    • number min: If given, numbers lesser than this will be rejected.
    • number max: If given, numbers greater than this will be rejected.
    • boolean strict: If false, strings will be forced to integers, and floats will be floored. Default false.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsFloat(msg)

Quaesitor.V.IsFloat(options, msg)

Returns a Validator that throws if the value is not a float.

  • object options:
    • number min: If given, numbers lesser than this will be rejected.
    • number max: If given, numbers greater than this will be rejected.
    • boolean strict: If false, strings will be forced to floats. Default false.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsArray(msg)

Quaesitor.V.IsArray(options, msg)

Returns a Validator that throws if the value is not an array.

  • object options:
    • number minlength: If given, arrays with less items than this will be rejected.
    • number maxlength: If given, arrays with more items than this will be rejected.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsObject(msg)

Quaesitor.V.IsObject(options, msg)

Returns a Validator that throws if the value is not an object.

  • object options: No options for this validator.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsBoolean(msg)

Quaesitor.V.IsBoolean(options, msg)

Returns a Validator that throws if the value is not a boolean.

  • object options: No options for this validator.
  • string msg: If set, will return this message instead.

Quaesitor.V.IsFile(msg)

Quaesitor.V.IsFile(options, msg)

Returns a Validator that throws if the value is not a Multipart file.

  • object options:
    • number minsize: If given, files smaller than this (in bytes) will be rejected.
    • number maxsize: If given, files larger than this (in bytes) will be rejected.
  • string msg: If set, will return this message instead.

Quaesitor.ErrorChains.DisplayableErrors()

Returns an exception chain handler that will display an error page if the caught exception is a DisplayableError, otherwise falls through.

Quaesitor.ErrorChains.PrintAllErrors()

Returns an exception chain handler that will display an error page for all exceptions. Not for production use.

Quaesitor.ErrorChains.JSONErrors(errorHandler)

Returns an exception chain handler that will return JSON if the caught exception is a DisplayableError. Otherwise, it returns a 500 Internal Server Error. If req.showErrorTraces was previously set to true, trace data will be included in the body.

  • function errorHandler: Function to call when this handler is executed with a non-displayable error. By default, logs to console.error.

Creating your own validator

A validator must be a function that takes the following arguments key, value, context, res, req:

  • string key: The key to be tested.
  • mixed value: The value to be tested.
  • string context: The location of the value (e.g. body, query, params)
  • Response res: The Response for this test
  • Request req: The Request for this test

Your validator must do one of the following:

  • return any value, in which case that will be the accepted value that will go onto the next test or be accepted;
  • return a Quaesitor.Validation.ValidationStop (create one with new Quaesitor.Validation.ValidationStop(acceptedValue)), in which case its value will immediately be accepted;
  • throw an error (preferably a Quaesitor.Errors.ValidationError).