fuhttp-ts
v1.2.1
Published
a HTTP-server written in TypeScript using Node.js
Downloads
6
Readme
fuhttp-ts
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)
Setup
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
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.
Documentation
Server
The Server class are used for handling HTTP requests and responses.
Constructor
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.
Add Route
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
Route lookup
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.
Add Middleware
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
Error Handling
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) {
...
});
Start Server
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();
Route
The Route class for parsing and matching incoming HTTP-requests based on an URL
Constructor
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
.
Add Url-Patterns
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
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
.
Middleware
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
Middlewares
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:
CORS
Enables cross-origin requests on the server.
...
let cors = new Cors({...}); // See documentatino for options below
server.use(cors);
Options
- 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
BodyJsonParse
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);
}
JsonResponse
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"
}
Custom
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
}
}
Examples
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
TODOs
- [ ] ~~Support for
connect
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
Release History
See the changelog
License
© Fosen-Utvikling AS, 2016 - 2018. Licensed under a MIT license