Web-server for nodejs written in TypeScript
, highly inspired by this blog post. The server is built on top of the http
package in nodejs.
Why another web-server package for nodejs
? because I can (and because I didn't need all features provided by e.g. express)
Install the package
npm install fuhttp-ts
fuhttp-ts
can now be included within your nodejs project using:
var fuhttp = require('fuhttp-ts');
For TypeScript:
import * as fuhttp from 'fuhttp-ts';
Manual installation requires a clone of the repo.
Install dependencies defined in package.json
npm install
Run the rebuild
script, as defined in packages.json
. This will delete the build/
folder, compile all .ts
files, create build/
folder where all .js
files will reside, and append the license banner to each output file.
The Server class are used for handling HTTP requests and responses.
Creating a new http-server:
constructor(port: number, host: string = null)
Where port
is the tcp/ip-port which the server will listen for.
If host
is set, only connections from host
will be accepted.
var server = new fuhttp.Server(8080, 'localhost');
Here, connections made from client with origin-ip localhost
connecting to the server on port 8080
is only accepted. If the HTTP-server should be made publicly available, hostname
should be ignored.
Adding a Route
to match with an incoming HTTP-request:
Where route
is a set of url-patterns. See Route for more information
add(path: string, route: Route): void
path
is the parent sub-url, where route is accessible from.
let route = new Route('api');
... // Setup routes
server.add('/api', route);
server.add('/test', route);
The server can now be accesses the defined route
object, both from /api
and /test
Routing is implemented in a linked list inspired manner. The server only contains a parent route, which has a reference to its sub-routes. The routing uses this implementation to support faster lookup of routes for parsing, as opposed needing to loop through all added routes to find a matching route.
A middleware is a set of instructions run before each request in handled by the Routes. See the Middleware section for more information about the available Middlewares
use(middleware: IMiddleware): void
The server may throw errors, e.g. if a route is not found.
This is where the Error Functions come into play. The Server
comes with some predefined error-handling functions, these can however be overwritten, which they also probably should.
Status Code | Type | Event name | Method Name |
---|---|---|---|
400 | Request Error | request | onRequestError(error, response) |
444 | Response Error | response | onResponseError(error, response) |
404 | Route Not Found | notfound | onNotFoundError(response) |
405 | Method Not Allowed | notallowed | onMethodNotAllowed(supportedMethods, response) |
413 | Request Data Too Large | overflow | onOverflowError(response) |
NA | Client Emits Error | clientError | onClientError(error, socket) |
NA | Server Closes | close | onClose() |
NA | Client Request A HTTP Upgrade | upgrade | onUpgrade() |
NA | Exception thrown from server method | exception | onException() |
Each error-handler can be set using the method as described in Method Name
, or through the more general method:
on(event: string, func: Function): boolean
Where event
is the Event Name
as defined in the table above.
The provided example is both equivalent, defining a function to run when there's an error with a HTTP-request
server.onRequestError = function(error: Error, response: http.ServerResponse) {
...
};
server.on('request', function(error: Error, response: http.ServerResponse) {
...
});
For the server to start accepting incoming requests, the server needs to be started:
listen(): void
Your HTTP-server is now able to handle incoming HTTP-requests, as configured in your constructor. If no routes are added at this point, the application will throw an error.
server.listen();
The Route class for parsing and matching incoming HTTP-requests based on an URL
constructor(routeName: string = null)
Where routeName
is the parent-pattern that will be matched against all incoming requests.
let route = new Route('api');
If the server is running locally, the route
will be accessible from the url http://localhost/api
. All incoming requests will be split on the first /
(after the hostname), and a matching route will be searched for within that route.
let r1 = new Route('user');
let r2 = new Route('company');
When the server receives a HTTP-request: [hostname]/user
, the r1
route will search for a match from one of its registered patterns, and ignore r2
. This would prevent the HTTP-server from searching all the defined routes in both r1
and r2
.
Requests against the HTTP-server can be done using these common HTTP-methods:
- GET
- POST
- DELETE
- PUT
What method to use when is really up to you, but you should however try to follow the convention. For a simple guide, visit restapitutorial.com
The format of each function is defined as following:
requestUrl: string, func: (req: http.IncomingMessage, res: http.ServerResponse, ...params: any[]) => void
requestUrl
is the pattern which the route should match against.
Parameters are defined using the :
notation infront of a keyword.
func
is the function to call when a HTTP-request url matches the defined pattern in requestUrl
. When a route matches, the first two parameters will always be:
req
- The actual request made by a user, see the documentation on nodejs.orgres
- The response to send to the user, see the documentation on nodejs.org
An undefined number of parameters is passed to func
depending on the defined pattern in requestUrl
. If no parameters is defined in requestUrl
, only the first two parameters will be set.
route.get('api/:id', function(req, res, id) {
// HTTP/GET
// Variable id corresponds to data passed in :id of the requestUrl, e.g. api/1 => id = 1
...
});
route.get('api/name/:name', function(req, res, name) {
// HTTP/GET
// api/name/hello => name = hello
...
});
route.get('user/:id', function(req, res, id, optional) {
// HTTP/GET
// user/1 => id = 1
...
});
route.get('hello/world', function(req, res) {
});
route.post(...)
, route.delete(...)
, route.put(...)
can be replaced with route.get(...)
in the provided example.
Sub-routing is a way of combining or creating nested routes, and is possible through
add(path: string, route: Route): void
let route = new Route();
let userRoute = new Route();
let baseRoute = new Route();
route.add('/user', user);
route.add('/base', base);
Here, the userRoute
will be accessible through /user
, and baseRoute
through base/
NOTE: If both route
and baseRoute
has a set of colliding route-patterns, only the parent-route will be matched, in this case route
.
Each Route can run a middleware before a matched route is called. As opposed to a server middleware, which is run on each request, a route middleware is only run for a single route. Use-cases could be to lock-down certain routes for authentication.
use(middleware: IMiddleware): void
More information about middlewares, and the different types can be found in the middleware section below
A Middleware is a set of instructions to run before a matching route is searched for. Middlewares are used for altering the HTTP-request and HTTP-response objects before they are passed to a matching route function.
Multiple middlewares are included in this package:
Enables cross-origin requests on the server.
...
let cors = new Cors({...}); // See documentatino for options below
server.use(cors);
- cookies: boolean
- default = false
- Whether the allow-credentials header should be set, supporting the use of cookies with CORS (Access-Control-Allow-Credentials)
- methods: HTTP_METHODS[]
- default = null => uses HTTP-method used in HTTP-request
- Supported http-methods for a route, defaults to current request method (Access-Control-Request-Method)
- maxage: number
- default = 1
- How many seconds a response to be cached for CORS (Access-Control-Max-Age)
The CORS middleware are implemented following the guide(s) found at enable-cors.org
Parsing of body-data to json
, using querystring
or JSON.parse
.
...
server.use(new BodyJsonParse());
This middleware exposes a .body
variable as an object type to the http.ServerResponse
parameter.
function(request, response) {
let bodyData:{} = request.body;
response.write('post id: ' + bodyData.id);
}
Generates a json response from an input js object. Exposes a method json()
to the response
object, and automatically ends the response.
function(request, response) {
response.json({
id: 12,
description: "lorem ipsum"
});
}
HTML response:
{
"id": 12,
"description": "lorem ipsum"
}
Each middleware is implementing the IMiddleware
interface, which requires the alter(...)
method to be implemented.
alter(request: http.IncomingMessage, response: http.ServerResponse): boolean;
All middlewares is required to return a boolean
value. If a middleware is returning false
, the request will stop executing.
import {IMiddleware} from 'fuhttp';
class MyCustomMiddleware implements IMiddleware {
public alter(req, res): boolean {
... // Do some tweaks on the req / res objects here
return true; // return false if you want to stop execution
}
}
Multiple examples are found in the examples/
folder.
var fuhttp=require('fuhttp-ts'); // Load package
var myRoute = new fuhttp.Route();
myRoute.get("hello/:name", function(req, res, name) {
res.write("Hello, your name is " + name);
res.end();
});
var server = new fuhttp.Server(5000); // Defined the port number which the http-server should accept connections
server.add("api", myRoute); // Add the myRoute for parsing request URIs and call appropriate route
server.listen(); // Start listening for incoming requests
-
Support forconnect
middlewares- As the connect middlewares expectes an input parameter
next()
this idea was scrapped, as I'm not a fan of this design structure
- As the connect middlewares expectes an input parameter
- Test middlewares?
- Performance testing, and comparison of other libraries
See the changelog
© Fosen-Utvikling AS, 2016 - 2018. Licensed under a MIT license