@jnode/server
v2.2.3
Published
Simple web server package for Node.js.
Maintainers
Readme
@jnode/server
Simple web server package for Node.js.
Installation
npm i @jnode/serverQuick start
Import
const { createServer, routerConstructors: r, handlerConstructors: h } = require('@jnode/server');Start a simple Hello, world. server
const server = createServer(
// use text handler to respond with text
h.Text('Hello, world.')
);
// listen to port 8080
server.listen(8080);Start a complex file and API server
let requestCount = 0;
const server = createServer(
// use path router to route the request
r.Path(
null, // nothing should be here because a basic path is `/`
{
// a request counter api
// `@` makes sure the path ends here
'@GET /api/request-count': h.JSON({ count: requestCount }),
// a static file service
// using `r.Function(route)` to count requests before continuing
'GET /files': r.Function(() => {
requestCount++;
// return a folder handler; it will send files for remaining path segments
return h.Folder('./static-files/');
})
}
)
);
// listen to port 8080
server.listen(8080);How it works?
Our world-leading router-handler framework brings you a simple, fast, and extensible development experience.
Here's what @jnode/server (shortened as JNS) will do:
- Receive a request.
- Use routers to route for a handler.
- Use a handler to complete the request.
Pretty simple, isn't it?
Further, a router has a method .route(env, ctx) which returns either another router (to continue routing) or a handler (which has a method .handle(ctx, env) that will be executed to complete the request).
Also, we provide some powerful built-in routers and handlers so you can start building your own web server now! (Learn more in the reference). And you can find more routers or handlers on npm with the query @jnode/server-<name> (built by the JNode team) or jns-<name> (built by the community).
Reference
server.createServer(router[, options])
routerrouter | handler-extended Same as the full filled return value ofrouter.route(env, ctx).options<Object>maxRoutingSteps<number> The max steps for routing; when exceeded but still getting another router, it'll throw the client a 508 error. Default:50.enableHTTP2<boolean> Enable HTTP/2 support (withnode:http2Compatibility API). Default:false.codeHandlers<Object> Global status code handlers. Keys are HTTP status codes, values are handlers-extended.- Options in
http.createServer(),https.createServer(),http2.createServer(), orhttp2.createSecureServer().
- Returns: <server.Server>
We will check if options.key and options.cert exist to decide whether to enable TLS or not. Note that most browsers reject insecure HTTP/2.
Class: server.Server
- Extends: <EventEmitter>
new server.Server(router[, options])
routerrouter | handler-extended Same as the return value ofrouter.route(env, ctx).options<Object> Same asserver.createServer().
More details in server.createServer().
Static method: Server.route(router[, env, ctx, options])
routerrouter | handler-extended Same as the return value ofrouter.route(env, ctx).env<Object> Same asenvofrouter.route(env, ctx).ctx<Object> Same asctxofrouter.route(env, ctx).options<Object>maxRoutingSteps<number> The max steps for routing; when exceeded but still getting another router, it'll return number508. Default:50.
- Returns: <Promise> Fulfills with a router or handler-extended. Simply, anything which is not a router. Will return
404if router returnsundefinedornull.
Please note that this function will modify env.i to count routing steps for safety.
server.listen()
Starts the server listening for connections. This method is identical to server.listen() from net.Server.
server.close(...args)
Closes the server. This method is identical to server.close() from net.Server.
server.throw(...args)
Emits an error event 'e' with the provided arguments. Useful for custom error handling and logging.
Event: 'e'
Emitted when an error occurs during routing or handling. The event includes the error object, env, and ctx.
Event: 'warn'
Emitted when a non-critical warning occurs, such as an invalid request URL.
Class: server.Router
This class is a base interface that defines the structure of a router; it does not contain built-in logic. Any object with method .route(env, ctx) will be viewed as a router.
router.route(env, ctx)
env<Object>path<string[]> The request path split by'/'and excluding the first segment (which is always an empty string since the path always starts with'/'). E.G.'/a/b'will become[ 'a', 'b' ], and/a/will become[ 'a', '' ].pathPointer<number> The path index pointer. Use it in your own router if needed to ensure capability withPathRouter.host<string[]> The request host split by.and reversed. E.G.'example.com'will become[ 'com', 'example' ].hostPointer<number> The host index pointer. Use it in your own router if needed so they could be compatible with the coreHostRouter.codeHandlers<Object> Keys are HTTP status codes, and values are handlers-extended.i<number> How many routers the request has gone through; do not change it in your own routers because the main loop increments it.
ctx<Object>req<http.IncomingMessage> | <http2.Http2ServerRequest> The request object.res<http.ServerResponse> | <http2.Http2ServerResponse> The response object.url<URL> The parsed request URL.server<server.Server> The server instance.path<string> The request path (pathname part of the URL).host<string> The request hostname.method<string> The HTTP request method (e.g.,'GET','POST').headers<Object> The request headers.identity<Object> Identity information for the requesting client.params<Object> The parameters including query string parameters; could also be injected by routers.body<stream.Readable> The request body stream (same asreq).
Returns: <Promise> | <router> | handler-extended Fulfills with a router or handler-extended. Promise is not strictly required; synchronous functions are also acceptable.
The env object contains metadata mainly for the routing process, while the ctx object is mainly for the handling process and used to store custom properties in most cases.
Here is an example custom router:
const { h: handlerConstructors } = require('@jnode/server');
// meow router, always responds with the text 'Meow!' if the request path contains any 'meow'
// (ignore why we need this)
class MeowRouter {
constructor(next) {
this.next = next;
}
// this makes the class a router
route(env, ctx) {
if (env.path.includes('meow')) {
return h.Text('Meow!');
}
return this.next;
}
}As for exporting, you could make a factory function like what we done in official routers:
// exporting
module.exports = {
MeowRouter, // original class
routerConstructors: {
Meow: (...args) => new MeowRouter(...args)
}
};If you even want to publish your routers or handlers on npm (why not?), feel free to name your package as jns-<name> so other developers will know it's for @jnode/server! E.G. jns-meow.
Class: server.Handler
This class is a base interface that defines the structure of a handler; it does not contain built-in logic. Any object with method .handle(ctx, env) will be viewed as a handler.
handler.handle(ctx, env)
ctx<Object> Same asctxofrouter.route(env, ctx).env<Object> Same asenvofrouter.route(env, ctx).
Handlers can throw a numeric error code to call codeHandlers, e.g., throw 404.
Here is an example custom handler:
// random int handler, returns a random integer
class RandomIntHandler {
constructor(from, to, options) {
this.from = from;
this.to = to;
this.options = options;
}
// this makes the class a handler
handle(ctx, env) {
const num = String(Math.floor(Math.random() * (this.to - this.from + 1)) + this.from);
const headers = this.options.headers ?? {};
headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain';
headers['Content-Length'] = Buffer.byteLength(num, 'utf8');
ctx.res.writeHead(this.options.statusCode ?? 200, headers);
ctx.res.end(num);
}
}handler-extended
handler-extended includes a standard handler and the following types:
- Number status code, we will find the actual handler from
env.codeHandlers. - String,
Buffer,Uint8Array, orstream.Readableas body, only for simple situations because you could not modify status code or headers. - Function does the same thing as
handler.handle(ctx, env).
server.mimeTypes
- Type: <Object>
A mapping of file extensions (.<ext>) to their corresponding MIME types.
Built-in routers
We provide two methods to use them:
newbased, use it vianew server.<Name>Router(...).- Factory functions, use it via
server.routerConstructors.<Name>(...).
Router: PathRouter(end, map)
endrouter | handler-extended Used when the path resolver came to the end (env.pathPointer >= env.path.length).map<Object>/<path_segment>[/<path_segment>...]router | handler-extended A simple path segment routing. E.G.,'/meow': 'Meow!'(ignoring later segments,'/meow/something'will also have the same result as'/meow'),'/cats': r.Path(null, { '/meow': 'Meow!! Meow!!' })or'/cats/meow': 'Meow!! Meow!!'.@/<path_segment>[/<path_segment>...]router | handler-extended Used when the path resolver ends here; equals to'/<path_segment>': r.Path(<value>). E.G.,'@/meow': 'Meow!'(only works for path'/meow'but not'/meow/something').<METHOD>/<path_segment>[/<path_segment>...]router | handler-extended Used when the method matches; equals to'/<path_segment>': r.Method({ '<METHOD>': <value> }). E.G.,'GET/meow': 'Meow!'(only works for HTTP method'GET').@<METHOD>/<path_segment>[/<path_segment>...]router | handler-extended Used when both the path resolver ends and the method matches; equals to'/<path_segment>': r.Path(r.Method({ '<METHOD>': <value> })). E.G.,'@GET/meow': 'Meow!'(only works for path'/meow'but not'/meow/something'and request method is'GET').*router | handler-extended Any path segment. E.G.'*': h.Text('Meow? Nothing here!', { statusCode: 404 })./%:<path_parameter_name>router | handler-extended Match any segment (if exists) and save the segment toctx.paramsby<path_parameter_name>. Do the similar thing asPathArgRouter.
PathRouter is probably the most important router; almost every server needs it!
Please note that when defining only /%:arg/b and /a/c, requesting /a/b WILL NOT return the value of /%:arg/b. Instead, it will return 404. This limitation is in place to improve performance, and we believe in designing a great API. If you still need this functionality, you can build your own router.
By the way, if you’re looking for a universal matching character, use '/%:' instead of '/*' (this will match to '*' in a literal sense).
How it works?
This section delves into the intricacies of how PathRouter functions. If you’re not interested in this topic, feel free to skip it.
Everything begins with the constructor. When we receive your map, we parse it into an internal structure. Here's an example:
// the map you passed in
map = {
'/a': 'A!',
'/a/b': 'B!',
'@/a/b': 'C!',
'GET/a': 'D!',
'@GET/a/b': 'E!',
'@GET/%:arg/c': 'F!',
'*': 'G!'
};
// we will parse into
parsed = {
'/a': {
'*': { // note that the * here doesn't count for path resolver
// object of methods
'*': 'A!',
'GET': 'D!'
},
'/b': {
'*': {
// object of methods
'*': 'B!'
},
'@': {
'*': 'C!',
'GET': 'E!'
}
}
},
':': {
'/c': {
'@': {
'GET': 'F!',
'::GET': [ 'arg' ]
}
}
},
'*': 'G!'
};This format allows for fast and flexible path routing while maintaining simplicity for developers. However, as mentioned earlier, some specialized path matching will not be supported.
Router: HostRouter(end, map)
endrouter | handler-extended Used when the host resolver came to the end (env.hostPointer >= env.host.length).map<Object>.<host_segment>[.<host_segment>...]router | handler-extended A simple host segment routing (reversed). E.G.,.example.comwill matchexample.com,.localhostwill matchlocalhost.@.<host_segment>[.<host_segment>...]router | handler-extended Used when the host resolver ends here. E.G.,@.example.com(only works for exactlyexample.combut notsub.example.com).*router | handler-extended Any host segment..%:<host_parameter_name>router | handler-extended Match any segment (if exists) and save the segment toctx.paramsby<host_parameter_name>.
Router: MethodRouter(methodMap)
methodMap<Object><METHOD>router | handler-extended A simple method routing. E.G.,'GET': h.Text('Hello!'),'POST': h.JSON({ ok: true }).*router | handler-extended Any method, used as fallback.
- Returns: <MethodRouter> Routes based on HTTP method, returns 405 if no method matches.
Router: FunctionRouter(fn, ext)
fn<Function> A function with signature(env, ctx, ext) => router | handler-extended.ext[<any>] Passed tofunc.
A simple router that allows you to implement custom routing logic.
Router: PathArgRouter(paramName, next)
paramName<string> The parameter name to save the current path segment.nextrouter | handler-extended The next router or handler to call after collecting the parameter.
Collects a path segment and saves it to ctx.params[paramName], then advances the path pointer and continues routing.
Router: HostArgRouter(paramName, next)
paramName<string> The parameter name to save the current host segment.nextrouter | handler-extended The next router or handler to call after collecting the parameter.
Collects a host segment and saves it to ctx.params[paramName], then advances the host pointer and continues routing.
Router: SetCodeRouter(codeHandlers, next)
codeHandlers<Object><STATUS_CODE>handler-extended A handler to respond when the specified HTTP status code is thrown. E.G.,404: h.Text('Not found!', { statusCode: 404 }),500: h.JSON({ error: 'Internal server error' }, { statusCode: 500 }).
nextrouter | handler-extended The next router or handler to call after setting up the code handlers.
Sets custom handlers for specific HTTP status codes, which will be used when handlers throw numeric error codes. The code handlers set by this router are merged with existing ones in env.codeHandlers.
Built-in handlers
We provide two methods to use them:
newbased, use it vianew server.<Name>Handler(...).- Factory functions, use it via
server.handlerConstructors.<Name>(...).
Handler: DataHandler(data[, options]) (alias: TextHandler)
data<string> | <Buffer> | <Uint8Array> | <stream.Readable> The data to send.options<Object>
Sends string, buffer, or stream data as response body. Automatically sets appropriate Content-Type and Content-Length headers.
Handler: FileHandler(file[, options])
file<string> Path to the file to serve.options<Object>statusCode<number> HTTP status code. Default:200.headers<Object> Additional headers to send.cache<boolean> | <number> Enable caching withETagandLast-Modifiedheaders. Iftrue, uses only conditional caching. If number, also setsCache-Control: max-age=<value>. Default:false.disableRange<boolean> Disable HTTP Range requests. Also automatically disabled whenstatusCodeis not 200. Default:false.disableHead<boolean> Disable HEAD request support. Default:false.highWaterMark<number> Stream high water mark in bytes. Default:65536.
Serves a single file with support for HTTP Range requests, caching headers, and ETag validation. Throws 404 if file not found or is not a regular file; throws 416 if range is invalid. Supports 304 Not Modified responses for conditional requests.
Handler: FolderHandler(folder[, options])
folder<string> Path to the folder to serve files from.options<Object>allowHiddenFile<boolean> Allows access to hidden directories and files (whose names begin with.).- Same as
FileHandleroptions.
Serves files from a folder based on remaining path segments. Automatically resolves paths and prevents directory traversal attacks. Internally uses FileHandler.
Handler: JSONHandler(obj[, options])
Sends a JavaScript object serialized as JSON with Content-Type: application/json; charset=utf-8.
Handler: RedirectHandler(location[, options])
Redirects the request to a specified location. Supports both absolute URLs and dynamic redirects based on remaining path segments.
Handler: FunctionHandler(func, ext)
func<Function> A function with signature(ctx, env, ext) => void | Promise<void>.ext[<any>] Passed tofunc.
Allows you to implement custom request handling logic directly within a function.
