Dero
Fast micro framework for Deno (support native HTTP/2 Hyper and std/http).
Features
- Fast (try to 1000+ route, your app still fast).
- Easy to use (inspired by expressjs middleware (req, res, next)).
- Routing Controller ready.
- Support Native HTTP/2 server with Hyper (required Deno 1.9 or higher).
Benchmarks
The benchmarks try to 1000 route and call http://localhost:3000/hello999. Example :
import { dero } from "https://deno.land/x/dero@0.2.1/mod.ts";
for (let i = 0; i < 1000; i++) {
dero.get('/hello' + i, (req, res) => {
res.body('hello route ' + i);
});
}
await dero.listen(3000);
wrk -t4 -c4 -d10s http://localhost:3000/hello999
Dero (native http)
Running 10s test @ http://localhost:3000/hello999
4 threads and 4 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.73ms 686.31us 12.67ms 83.95%
Req/Sec 366.39 18.15 404.00 75.75%
14611 requests in 10.02s, 1.83MB read
Requests/sec: 1458.55
Transfer/sec: 186.59KB
Dero (std/http)
Running 10s test @ http://localhost:3000/hello999
4 threads and 4 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.11ms 1.09ms 20.88ms 90.50%
Req/Sec 324.37 34.39 380.00 79.75%
12942 requests in 10.02s, 682.49KB read
Requests/sec: 1291.15
Transfer/sec: 68.09KB
Oak
Running 10s test @ http://localhost:3000/hello999
4 threads and 4 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.76ms 1.16ms 23.79ms 80.17%
Req/Sec 210.51 22.25 262.00 81.00%
8398 requests in 10.02s, 664.11KB read
Requests/sec: 837.86
Transfer/sec: 77.73KB
Opine
Running 10s test @ http://localhost:3000/hello999
4 threads and 4 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 9.43ms 26.38ms 284.45ms 97.61%
Req/Sec 176.44 31.29 222.00 73.98%
6916 requests in 10.02s, 364.71KB read
Requests/sec: 689.97
Transfer/sec: 36.39KB
Expressjs (nodejs)
Running 10s test @ http://localhost:3000/hello999
4 threads and 4 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.80ms 3.75ms 78.24ms 96.34%
Req/Sec 220.80 43.89 282.00 80.50%
8818 requests in 10.04s, 0.97MB read
Requests/sec: 878.39
Transfer/sec: 98.65KB
Usage
import { dero } from "https://deno.land/x/dero@0.2.1/mod.ts";
// or
// import { dero } from "https://x.nest.land/dero@0.2.1/mod.ts";
dero
.get("/hello", (req, res) => {
res.body("hello");
})
// or
.get("/hello-dero", (req, res) => {
res.header({ "x-powered-by": "anything" })
.status(201)
.body({ message: "With status and header" });
});
await dero.listen(3000);
Usage With Routing Controller
import { dero, Controller, Get, Status, Header, HttpRequest } from "https://deno.land/x/dero@0.2.1/mod.ts";
@Controller("/hello")
class HelloController {
@Get()
hello() {
return "hello";
}
@Header({ "x-powered-by": "anything" })
@Status(201)
@Get("-dero")
helloDero() {
return { message: "With status and header" };
}
}
dero.use({
class: [HelloController]
});
// or
// dero.use({
// prefix: "/api/v1",
// wares: [midd1, midd2],
// class: [HelloController]
// });
await dero.listen(3000);
Run
deno run --allow-net yourfile.ts
// or support native http (use --unstable flag)
deno run --allow-net --unstable yourfile.ts
Decorator
Controller Decorator
Controller decorator @Controller(path?: string).
...
@Controller("/hello")
class HelloController { }
...
Method Decorator
Method decorator like @Get(path?: string).
Available => @Get, @Post, @Put, @Delete, @Patch, @Head, @Options, @Any, @Trace, @Connect.
...
@Controller("/hello")
class HelloController {
@Get()
hello() {
return "hello";
}
}
...
Status Decorator
Set status on decorator like @Status(code: number).
...
@Controller("/hello")
class HelloController {
@Status(201)
@Post()
save() {
return "Created";
}
}
...
Header Decorator
Set header on decorator @Header(object | fn).
...
@Controller("/hello")
class HelloController {
@Header({
"Content-Type": "text/html"
})
@Get()
hello() {
return "<h1>Hello</h1>";
}
@Header((req, res) => {
let type = req.url.includes(".css") ? "text/css" : "text/plain";
return { "Content-Type": type };
})
@Get()
hello2() {
return Deno.readFile(yourpath);
}
}
...
Middlewares Decorator
Set Middlewares on decorator @Wares(mid1, mid2, mid3).
...
@Controller("/hello")
class HelloController {
// @Wares(mid1, mid2, mid3)
// or
// @Wares([mid1, mid2, mid3])
@Wares((req, res, next) => {
req.foo = "foo";
next();
})
@Get()
hello(req: HttpRequest) {
return req.foo;
}
}
...
Config (if you want)
...
// this code is example
dero.config({
useNativeHttp: boolean, /* default true */
useParseUrl: (req: HttpRequest) => any, /* default native */
useParseQuery: (qs: string) => any, /* default native */
});
...
Middleware
import { dero, Controller, Get, Wares } from "https://deno.land/x/dero@0.2.1/mod.ts";
@Controller("/hello")
class HelloController {
// inside handlers use @Wares
@Wares((req, res, next) => {
req.foo = "foo";
next();
})
@Get()
hello() {
return "hello";
}
}
// global middleware with dero.use(...middlewares)
dero.use(midd1, midd2);
dero.use({
// the middleware available only HelloController
wares: [midd1, midd2]
class: [HelloController]
});
await dero.listen(3000);
HttpRequest
import { HttpRequest } from "https://deno.land/x/dero@0.2.1/mod.ts";
Query
Query http://localhost:3000/hello?name=john
...
@Controller("/hello")
class HelloController {
@Get()
hello(req: HttpRequest) {
console.log(req.query);
return req.query.name;
}
}
...
Params
Params example => http://localhost:3000/hello/1
Standart params => /path/:id
Optional params => /path/:id?
Filtered params => /path/image/:title.(png|jpg)
All params => /path/*
...
@Controller("/hello")
class HelloController {
@Get("/:id")
hello(req: HttpRequest) {
console.log(req.params);
return req.params.id;
}
@Get("/:id?")
helloOptional(req: HttpRequest) {
return req.params.id || 'no params';
}
// only png and jpg extensions.
@Get("/image/:title.(png|jpg)")
getImage(req: HttpRequest) {
return req.params.title;
}
@Get("/all/*")
getAllParams(req: HttpRequest) {
// log: {"wild":["param1","param2"]}
return req.params || {};
}
}
...
Pond
like req.respond, req.pond help sending body, status, headers. req.pond(body, { status, headers }); where body is string, json, Uint8Array, Deno.Reader,
All HttpRequest
interface HttpRequest {
respond: (r: any) => Promise<void>;
pond: (body?: TBody | { [k: string]: any } | null, opts?: PondOptions) => Promise<void>;
proto: string;
url: string;
conn: Deno.Conn;
isHttps: boolean | undefined;
method: string;
headers: Headers;
body: Deno.Reader | null;
originalUrl: string;
params: { [k: string]: any };
_parsedUrl: { [k: string]: any };
path: string;
query: { [k: string]: any };
search: string | null;
};
HttpResponse
import { HttpResponse } from "https://deno.land/x/dero@0.2.1/mod.ts";
Header
header: (value?: object | string | undefined) => HttpResponse | string | Headers;
...
res.header({"key1": "value1"});
res.header({"key2": "value2"});
// or same as
res.header({"key1": "value1", "key2": "value2"});
let header = res.header();
console.log(header);
// => Headers {"key1":"value1","key2":"value2"}
console.log(res.header("key1"));
// => value1
// delete
header.delete("key1");
console.log(header);
// => Headers {"key2":"value2"}
...
@Controller("/hello")
class HelloController {
@Get()
hello(req: HttpRequest, res: HttpResponse) {
res.header({"content-type": "text/plain"}).body("Done");
}
}
...
Status
status: (code?: number | undefined) => HttpResponse | number;
...
res.status(201);
console.log(res.status() as number);
// => 201
...
@Controller("/hello")
class HelloController {
@Post()
hello(req: HttpRequest, res: HttpResponse) {
res.status(201).body("Created");
}
}
...
Body
body: (body?: json | string | Uint8Array | Deno.Reader) => Promise;
@Controller("/hello")
class HelloController {
@Post()
hello(req: HttpRequest, res: HttpResponse) {
res.body(json | string | Uint8Array | Deno.Reader);
}
}
...
Next
Next Function is a function to next step handler (on middleware).
example next
...
.use((req, res, next) => {
res.locals = {
username: "dero"
}
next();
})
...
listen(opts: number | object, callback?: (err?: Error, opts?: object) => void);
await dero.listen(3000);
// or
const cb = (err, opts) => {
if (err) console.log(err);
console.log("Running on server " + opts?.port);
}
await dero.listen(3000, cb);
// or
await dero.listen({ port: 3000, hostname: 'localhost' }, cb);
// or https
await dero.listen({
port: 443,
certFile: "./path/to/localhost.crt",
keyFile: "./path/to/localhost.key",
}, cb);
// or http/2 need deno 1.9.0 or higher
await dero.listen({
port: 443,
certFile: "./path/to/localhost.crt",
keyFile: "./path/to/localhost.key",
alpnProtocols: ["h2", "http/1.1"]
}, cb);
Simple error handling.
...
// error handling
dero.use((err: any, req: HttpRequest, res: HttpResponse, next: NextFunction) => {
res.status(err.code || 500).body({ message: err.message });
});
// not found error handling
dero.use("*", (req, res, next) => {
res.status(404).body({ message: `Url ${req.url} not found` });
});
...
The role of dero.use
// controllers object { class: Array, prefix?: string, wares?: Array }
use(controllers: DeroControllers): this;
// spread array middlewares
use(...middlewares: Array<THandler | THandler[]>): this;
// prefix string spread array middleware (for serve static here)
use(prefix: string, ...middlewares: Array<THandler | THandler[]>): this;