dragon
dragon copied to clipboard
⚡Fast , simple expressive web framework for deno 🦕.
Dragon.js
Fast ,simple expressive web framework for deno. If you need performance and good productivity, you will love it.
Features
- Developer friendly, very expressive and help the developer in their daily use, without sacrificing performance and security.
- Lightweight and modular design allows for a flexible framework.
- Focus on high performance.
- Middleware support, incoming HTTP request can be handled by a chain of middlewares and the final action.
- Excellent and fluent documentation.
Getting Started
Let's start registering a couple of URL withPaths and handlers:
import {
Application,
HttpRequest,
HttpResponse,
RequestMethod,
} from "https://deno.land/x/[email protected]/lib/mod.ts";
const app = new Application();
const r = app.routes();
r.withPath("/hello")
.withMethods(RequestMethod.GET)
.handleFunc(
async function (Request: HttpRequest, ResponseWriter: HttpResponse) {
ResponseWriter.end("Hello Dragon");
},
);
r.withPath("/demo")
.handleFunc(
async function (Request: HttpRequest, ResponseWriter: HttpResponse) {
ResponseWriter.end("Hello Dragon Demo");
},
);
app.listenAndServe({ port: 8080 });
console.log("🐉 Serveur listining");
Here we register two routes mapping URL withPath to handler. if an incoming
request URL matches one of the withPaths, the corresponding handler is called
passing. We believe development must be an enjoyable and creative experience to
be truly fulfilling (HttpRequest, HttpResponse) as parameters.
Documentation
Get started with Dragon, learn the fundamentals and explore advanced topics.
Table of content
- Installation
- Routing
- Requests
- Headers
- Responses
- Cookies
- Middlewares
- Handling CORS Requests
- Full Example
Installation
Assuming you’ve already installed Deno, create a directory to hold your application, and make that your working directory.
$ mkdir Dragon-app
$ cd Dragon-app
Creates an Dragon application. The Application class exported from Dragon
module and sets up the application with various options.
const app = new Application();
An instance of application has some optional properties as well:
-
proxyIpHeaderReturn header for identifying the originating IP address of a client connecting to a web server through an
HTTP proxyor aload balancer. -
hostnameA unique name for a computer or network node in a network. This defaults to
0.0.0.0. -
portNumbers used by protocols for operation of network applications.
-
certFileA concatenation of all Certificate Authority (CA).
-
keyFileThe associated private key.
-
secureThe listening will be over HTTPS.
Routing
Routing is made from the word route. It is used to determine the specific
behavior of an application. It specifies how an application responds to a client
request to a particular route, URI or withPath and a specific HTTP request
method (GET, POST, etc.). It can handle different types of HTTP requests.
1- Basic Routing
Dragon provides a very simple and expressive method of defining routes and behavior without complicated routing configuration files:
const r = app.routes();
r.withPath("/hello")
.withMethods(RequestMethod.GET)
.handleFunc(
async function (Request: HttpRequest, ResponseWriter: HttpResponse) {
ResponseWriter.end("Hello Dragon");
},
);
The optional options parameter specifies the behavior of the router.
-
maxParamLengthA custom length for parameters.
-
notFoundHandlerConfigurable Handler to be used when no route matches.
-
maxRoutesMaximum allowed routes.
2- Available Router Methods
The router allows you to register routes that respond to any HTTP verb: GET,
POST, PUT, DELETE
const r = app.routes();
r.withMethods(RequestMethod.GET);
Sometimes you may need to register a route that responds to multiple HTTP verbs.
const r = app.routes();
r.withMethods(RequestMethod.GET, RequestMethod.POST);
3- Route Parameters
Sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. You may do so by defining route parameters:
const r = app.routes();
r.withPath(/user\/(?<id>[0-9]{1,})/u)
.withMethods(RequestMethod.GET)
.handleFunc(
async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
): Promise<any> {
const { id: userID } = await Request.params();
ResponseWriter.end(`User with id ${userID}`);
},
);
You may define as many route parameters as required by your route.
🚨 Dragon uses regex named group in order to match parameters.
4- Named Routes
Named routes allow to get handler. You may specify a withName for a route by
chaining the name method onto the route definition:
const r = app.routes();
r.withPath("/user/profile")
.withMethods(RequestMethod.GET)
.withName("profile")
.handleFunc(
async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
): Promise<any> {
//
},
);
5- Fallback Routes
Using notFoundHandler option. you may define a route that will be executed
when no other route matches the incoming request:
const fallback = async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
) {
ResponseWriter.html`🤦 Page Not Found`.end();
return MiddlewareState.Cancel;
};
const r = app.routes({
notFoundHandler: fallback,
});
Request Object
The HttpRequest class provides an object represents the HTTP request and has
properties for the request query string, parameters, body, HTTP headers, and so
on.
An instance of request object has some methods associated as well:
-
expectsJsonQuickly determine if the incoming request expects a JSON response.
-
methodReturns the HTTP verb for the request.
-
urlReturns the full URL for incoming request.
-
urlQueryReturns the full URL for incoming request.
-
withPathReturns the request's withPath information
-
prefersDetermine which content type out of a given array of content types is most preferred by the request. If none of the provided content types are accepted by the request,
nullwill be returned. -
isXHRCheck if the request was an
_XMLHttpRequest_. -
hostNameReturns the
Hostheader field to a hostname. -
isIpv4Determines whether the host name is an IP address 4 bytes.
-
isIpv6Determines whether the host name is a valid IPv6.
-
contentLengthIndicates the size of the entity-body, in bytes, sent to the recipient.
-
bodyIt contains key-value pairs of data submitted in the request body.
-
bodyWithoutParserGet the body of the message without parsing.
-
contentTypeReturns the media type of the resource.
-
schemesReturns
httporhttpswhen requested with TLS. -
queryParamsReturns an array of object containing a property for each query string parameter in the route.
-
queryParamReturns specific query param.
-
paramsAn object containing properties mapped to the named route
parametersFor example, if you have the route /user/:name, then the "name" property is available asconst {name} = GetParams();This object defaults to {}. -
secureVerify if the request is secure
HTTPS.
Headers
The Headers interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing headers from the list of the request's headers.
You may retrieve a request header from the HttpRequest and HttpResponse
instance using the header or headers method. If the header is not present on
the request, null will be returned.
const HandlerFun = async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
) {
// Retrieves a message header value by the name.
const v1 = Request.header("X-Header-Name");
// Retrieves all message header values.
const v2 = Request.headers();
};
The hasHeader method may be used to determine if the request contains a given
header:
if (Request.hasHeader("X-Header-Name")) {
//
}
The delHeader method is used to remove given header if exists :
Request.delHeader("X-Header-Name");
The withHeader method is used to add a series of headers to the response
before sending it back to the user.
Request.withHeader("X-Header-One", "Header Value 1")
.withHeader("X-Header-Two", "Header Value 2")
.send();
💬 Keep in mind that most response methods are chainable, allowing for the fluent construction of response instances.
Response Object
All routes should return a response to be sent back to the user's browser. Dragon provides several different ways to return responses.
Let's see some methods of response object.
-
statusCodeSet the response status code. The status code is a 3-digit integer result code of the server's attempt.
-
withStatusSet an instance with the specified status code.
-
withContentLengthSet Content-Length field to
n. -
withLastModifiedSet the Last-Modified date using a
stringor aDate. -
withBodySet the response body.
-
htmlRenders a view and sends the rendered HTML string to the client.
-
jsonReturns the response in JSON format ,as well as set the
Content-Typeheader toapplication/json. -
redirectRedirect the client to another URL with optional response
statusdefaulting to 302. -
isRedirectStatusDetermines if a HTTP
Statusis aRedirectStatus(3XX). -
abortRise an HTTP error from the server.
-
endReturn a response.
Cookies
Cookies are small piece of information i.e. sent from a website and stored in user's web browser when user browses that website. Every time the user loads that website back, the browser sends that stored data back to website or server, to recognize user.
Let's define a new route in your Dragon app like set a new cookie:
const r = app.routes();
r.withPath("/demo")
.withMethods(RequestMethod.GET)
.handleFunc(
async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
): Promise<any> {
ResponseWriter.withCookie("id=a3fWa; Max-Age=2592000").end();
},
);
Middlewares
Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application.
💬 Middleware functions are always invoked in the order in which they are added.
Middleware is commonly used to perform tasks like body parsing for URL-encoded or JSON requests, cookie parsing for basic cookie handling.
Dragon provides build-in middlewares like:
- X-XSS-Protection
- X-Frame-Options
- CORSMethodMiddleware
1- Assigning Middleware To Routes
If you would like to assign middleware to specific routes, you shoud use
withMiddleware methods:
const middleware = async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
) {
console.log(Request.method());
return MiddlewareState.Next;
};
const r = app.routes();
r.withPath("/middleware/example")
.withMethods(RequestMethod.GET)
.withMiddleware(middleware)
.handleFunc(
async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
): Promise<any> {
//
},
);
💬 To pass the request deeper into the application, you must call the
MiddlewareState.Nexton the other hand you can useMiddlewareState.Cancelto terminate the middleware.
2- Middleware Groups
Sometimes you may want to group several middleware under a single key to make
them easier to assign to routes. You may accomplish this using the
withMiddlewareGroups:
const StartSession = async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
) {
// Code implementation.
return MiddlewareState.Next;
};
const VerifyCsrfToken = async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
) {
// Code implementation.
return MiddlewareState.Next;
};
const r = app.routes();
r.withPath("/grouped/middlewares/example")
.withMethods(RequestMethod.GET)
.withMiddlewareGroups("web", [StartSession, VerifyCsrfToken])
.handleFunc(
async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
): Promise<any> {
//
},
);
3- Global Middleware
If you want a middleware to run during every HTTP request to your application,
you should use globalMiddleware methods:
const middleware = async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
) {
//
return MiddlewareState.Next;
};
const r = app.routes();
r.withPath("/global/middlewares/example")
.withMethods(RequestMethod.GET)
.globalMiddleware(middleware)
.handleFunc(
async function (
Request: HttpRequest,
ResponseWriter: HttpResponse,
): Promise<any> {
// Code implementation.
},
);
Handling CORS Requests
CORS is shorthand for Cross-Origin Resource Sharing. It is a mechanism to allow or restrict requested resources on a web server depend on where the HTTP request was initiated.
👀 This policy is used to secure a certain web server from access by other website or domain.
CORSMethodMiddleware intends to make it easier to strictly set the
Access-Control-Allow-Methods response header.
Here is an example of using
CORSMethodMiddleware along with a custom
OPTIONS handler to set all the required CORS headers.
Full Examples
Here's a complete, runnable example of a small Dragon based server:
import { Application, HttpRequest, HttpResponse, RequestMethod } from "https://deno.land/x/[email protected]/lib/mod.ts";
const app = new Application();
const r = app.routes({ maxRoutes:1 });
r.withPath("/Dragon")
.withMethods(RequestMethod.GET)
.withName("root")
.handleFunc(async function (Request: HttpRequest, ResponseWriter: HttpResponse): Promise<void> {
//
ResponseWriter.withBody("Dragon").end();
});
app.listenAndServe({ port: 8080 });
}
console.log("🐉 Serveur listining");
Benchmarks
Machine: 7,6 GiB, Intel® Core™ i5-3210M CPU @ 2.50GHz × 4 , Intel® Ivybridge Mobile, 320,1 GB.
method: autocannon -c 100 -d 40 -p 10 localhost:8080 , taking the second
average
| Framework | Version | Router? | Results |
|---|---|---|---|
| Express | 4.17.1 | ✓ | 166k requests in 40.08s, 39.5 MB read |
| Fastify | 3.9.1 | ✓ | 1081k requests in 40.07s ,189 MB read |
| Oak | 4.0.0 | ✓ | 243k requests in 40.12s, 27 MB read |
| Dragon | 1.0.0 | ✓ | 416k requests in 40.21s, 37.1 MB read |
This is a synthetic, hello world benchmark that aims to evaluate the framework
overhead. The overhead that each framework has on your application depends on
your application, you should always benchmark if performance matters to you.
Contributing
We appreciate your help 👋!
We encourage you to contribute to Dragon! Please check out the guidelines about how to proceed.
Sponsors
We would like to extend our thanks to the following sponsors for funding Dragon development. If you are interested in becoming a sponsor, please visit the Dragon Open collective page.
Code of Conduct
In order to ensure that the Dragon community is welcoming to all, please review and abide by the Code of Conduct.
Security Issues
If you discover a security vulnerability in Dragon, please see Security Policies and Procedures.
Changelog
Detailed changes for each release are documented in the release notes.
People
-
The original author of Dragon is Yasser A.Idrissi.
License
The Dragon framework is open-sourced software licensed under the Apache-2.0 License.