Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/herudi/dero
Fast web framework for Deno (support native HTTP/2 Hyper and std/http).
https://github.com/herudi/dero
Last synced: 9 days ago
JSON representation
Fast web framework for Deno (support native HTTP/2 Hyper and std/http).
- Host: GitHub
- URL: https://github.com/herudi/dero
- Owner: herudi
- License: mit
- Created: 2021-04-05T08:28:33.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2021-12-12T03:37:49.000Z (almost 3 years ago)
- Last Synced: 2024-04-09T14:24:22.218Z (7 months ago)
- Language: TypeScript
- Homepage: https://dero.herudi.workers.dev
- Size: 328 KB
- Stars: 21
- Watchers: 5
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
## Dero
Fast web framework for Deno (support native HTTP/2 [Hyper](https://hyper.rs) and
std/http).[![License](https://img.shields.io/:license-mit-blue.svg)](http://badges.mit-license.org)
[![deno.land](https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%[email protected]%2Fmod.ts)](https://deno.land/x/dero)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blue.svg)](http://makeapullrequest.com)
![deps badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Fdep-count%2Fhttps%2Fdeno.land%2Fx%2Fdero%2Fmod.ts)
![cache badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Fcache-size%2Fhttps%2Fdeno.land%2Fx%2Fdero%2Fmod.ts)
[![nest.land](https://nest.land/badge.svg)](https://nest.land/package/dero_framework)## Features
- Native HTTP/2 support.
- Controller decorator support.
- Middleware support.
- Includes body parser (json and urlencoded).
- Class Validator (based on
[class-validator](https://github.com/typestack/class-validator)).[See examples](https://github.com/herudi/dero/tree/main/examples)
## Benchmark
Simple benchmark.
`autocannon -c 100 http://localhost:3000/`
Pure Deno
| Name | Req/sec | Throughput |
| -------- | ------- | ---------- |
| Native | 21433 | 2.5 MB |
| std/http | 14569 | 626 KB |Dero
| Name | Req/sec | Throughput |
| ----------- | ------- | ---------- |
| Dero native | 20672 | 2.3 MB |
| Dero native | 13895 | 544 KB |## Installation
### deno.land
```ts
import {...} from "https://deno.land/x/[email protected]/mod.ts";
```### nest.land
```ts
import {...} from "https://x.nest.land/[email protected]/mod.ts";
```## Usage
```ts
import {
BaseController,
Controller,
Dero,
Get,
} from "https://deno.land/x/[email protected]/mod.ts";@Controller("/user")
class UserController extends BaseController {
@Get()
getUser() {
return "Hello";
}@Get("/:name")
getUserByName() {
const { name } = this.request.params;
return name;
}
}class Application extends Dero {
constructor() {
super();
this.use({ class: [UserController] });
}
}await new Application().listen(3000, () => {
console.log("Running on port 3000");
});
```## Run
```bash
deno run --allow-net yourfile.ts
```## Deploy
deploy to https://deno.com/deploy
```ts
import {
BaseController,
Controller,
Dero,
Get,
} from "https://deno.land/x/[email protected]/mod.ts";@Controller("/")
class HelloController extends BaseController {
@Get()
hello() {
return "Hello Deploy";
}
}class Application extends Dero {
constructor() {
super();
this.use({ class: [HelloController] });
}
}const app = new Application();
app.deploy();
// or manual
// addEventListener('fetch', (evt: any) => {
// evt.respondWith(app.handleEvent(evt));
// })
```## Decorator
### Controller Decorator
Controller decorator @Controller(path?: string).
```ts
...
@Controller("/hello")
class HelloController extends BaseController { }
...
```### Method Decorator
Method decorator like @Get(path?: string).
> Available => @Get, @Post, @Put, @Delete, @Patch, @Head, @Options, @Any,
> @Trace, @Connect.```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
return "hello";
}
}
...
```### Status Decorator
Set status on decorator like @Status(code: number).
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Status(201)
@Post()
save() {
return "Created";
}
}
...
```### Header Decorator
Set header on decorator @Header(object | fn).
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Header({ "Content-Type": "text/html" })
@Get()
hello() {
return "Hello
";
}
}
...
```### Middlewares Decorator
Set Middlewares on decorator @Wares(...middlewares).
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Wares((req, res, next) => {
req.foo = "foo";
next();
})
@Get()
hello() {
return this.request.foo;
}
}
...
```### Inject Decorator
```ts
...
class UserService {
async findAll(){
const data = await User.findAll();
return { status: 200, data };
}
}@Controller("/user")
class UserController extends BaseController {@Inject(UserService)
private readonly userService!: UserService;@Get()
findAll() {
return this.userService.findAll();
}
}
...
```### View Decorator
> requires viewEngine
```ts
...
@Controller("/user")
class UserController extends BaseController {@View("index")
@Get()
index() {
return {
title: "Welcome"
};
}
}
...
```### Type Decorator
set content type on decorator.
```ts
...
@Controller("/user")
class UserController extends BaseController {@Type("html")
@Get()
index() {
return `Hello
`;
}
}
...
```### Validate Decorator
Body validator. see doc
[class-validator](https://github.com/typestack/class-validator).
@Validate(dtoClass, options?);```ts
...
import {
BaseController,
Controller,
Validate,
Post,
Dero
} from "https://deno.land/x/[email protected]/mod.ts";// class validator
import {
validateOrReject,
IsString,
IsEmail
} from "https://cdn.skypack.dev/class-validator?dts";// validate user
class User {@IsString()
username!: string;@IsEmail()
email!: string;
}@Controller("/user")
class UserController extends BaseController {// validate decorator
@Validate(User)
@Post()
save() {
return "Success save"
}
}class Application extends Dero {
constructor() {
super({
// register class validator
classValidator: validateOrReject
});// add class controller
this.use({ class: [UserController] });
}
}await new Application().listen(3000);
...
```## Config
```ts
...
class Application extends Dero {
constructor() {
super({
nativeHttp: true,
env: "development",
parseQuery: qs.parse,
bodyLimit: {
json: "3mb",
urlencoded: "3mb",
raw: "3mb",// to disable body parser please set 0
// json: 0,
// urlencoded: 0,
// raw: 0,
}
})// more
}
}
...
```## Middleware
```ts
...
@Controller("/hello")
class HelloController extends BaseController {// inside handlers use @Wares
@Wares(midd1, midd2)
@Get()
hello() {
return "hello";
}
}class Application extends Dero {
constructor() {
super();// global middleware with .use(...middlewares)
this.use(midd1, midd2);// the middleware available only HelloController
this.use({
wares: [midd1, midd2]
class: [HelloController]
});
}
}
...
```## HttpRequest
```ts
import { HttpRequest } from "https://deno.land/x/[email protected]/mod.ts";
```### request.query
Query http://localhost:3000/hello?name=john
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
return this.request.query;
}
}
...
```### request.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/*
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get("/:id")
hello() {
const { id } = this.request.params;
return id;
}@Get("/:id?")
helloOptional() {
return this.request.params.id || 'no params';
}// only png and jpg extensions.
@Get("/image/:title.(png|jpg)")
getImage() {
const { title } = this.request.params;
return title;
}@Get("/all/*")
getAllParams() {
// log: {"wild":["param1","param2"]}
return this.request.params || [];
}
}
...
```### request.parsedBody
output body but was parsed to json.
### request.getBaseUrl
get base url
### request.pond
like request.respond.
```ts
...
@Get()
hello() {
this.request.pond("hello", {
status: 200,
headers: new Headers()
})
}
...
```### request.getCookies
get request cookies
```ts
...
@Get()
withCookies() {
const cookies = this.request.getCookies();
return cookies;
}@Get()
withCookiesDecode() {
const cookies = this.request.getCookies(true);
return cookies;
}
...
```### All HttpRequest
```ts
class HttpRequest {
respond!: (r: any) => Promise;
parsedBody!: { [k: string]: any };
pond!: (
body?: TBody | { [k: string]: any } | null,
opts?: PondOptions,
) => Promise;
getCookies!: () => Record;
proto!: string;
url!: string;
conn!: Deno.Conn;
secure: boolean | undefined;
bodyUsed: 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;
getBaseUrl!: () => string;
}
```## HttpResponse
```ts
import { HttpResponse } from "https://deno.land/x/[email protected]/mod.ts";
```### response.header
header: (key?: object | string | undefined, value?: any) => HttpResponse |
string | Headers;```ts
...
// key and value
response.header("key1", "value1");// with object
response.header({ "key2": "value2" });// multiple header
response.header({
"key3": "value3",
"key4": "value4"
});// get header
console.log(response.header());
// => Headers {
// "key1":"value1",
// "key2":"value2",
// "key3":"value3",
// "key4":"value4",
// }// get header by key
console.log(response.header("key1"));
// => value1// delete key1
response.header().delete("key1");
console.log(response.header());
// => Headers {
// "key2":"value2",
// "key3":"value3",
// "key4":"value4",
// }// convert to json object
console.log(Object.fromEntries(response.header().entries()));
// => {
// "key2":"value2",
// "key3":"value3",
// "key4":"value4",
// }// reset header
response.header(new Headers());
console.log(response.header());
// => Headers { }
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const res = this.response;
res.header("content-type", "text/html").body("Done
");
}
}
...
```### response.type
Shorthand for res.header("Content-Type", yourContentType);
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const res = this.response;
res.type("html").body("Done
");
}
}
...
```### response.status
status: (code?: number | undefined) => HttpResponse | number;
```ts
...
// set status
res.status(201);// get status
console.log(res.status());
// => 201
...
@Controller("/hello")
class HelloController extends BaseController {@Post()
hello() {
const res = this.response;
res.status(201).body("Created");
}
}
...
```### response.body
response.body: (body?: json | string | Uint8Array | Deno.Reader) =>
Promise;```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const res = this.response;
res.body(json | string | Uint8Array | Deno.Reader);
}
}
...
```### response.json
response.json: (jsonData) => Promise;
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const res = this.response;
res.json({ name: "john" });
}
}
...
```### response.file
response.file: (pathfile: string, options?) => Promise;
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
this.response.file("path/to/file.txt");
}@Get("/test")
hello2() {
this.response.file("path/to/file.txt", {
etag: true,
basedir: Deno.cwd() + "/myfolder"
});
}
}
...
```### response.download
response.download: (pathfile: string, options?) => Promise;
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
this.response.download("path/to/file.txt");
}@Get("/test")
hello2() {
this.response.download("path/to/file.txt", {
filename: "myCustomFileName.txt",
basedir: Deno.cwd() + "/myfolder"
});
}
}
...
```### response.cookie
response.cookie: (name, value, options?) => this;
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const res = this.response;
res.cookie("session", "admin", { encode: true, maxAge: 20000 }).body("hello");
}@Get("/home")
home() {
const req = this.request;
res.body(req.getCookies());// decode if encode true
res.body(req.getCookies(true));
}
}
...
```### response.clearCookie
response.clearCookie: (name) => void;
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const res = this.response;
res.clearCookie("session");
res.body("hello");
}
}
...
```### response.redirect
response.redirect: (url, status?) => Promise;
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
this.response.body("Hello")
}@Get("/redirect")
redirect() {
this.response.redirect("/hello")
}
}
...
```### response.view
response.view: (pathfile, params, ...args) => Promise
> requires viewEngine
```ts
...
@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
this.response.view("index", {
name: "john"
})
}}
...
```### response.return
Mutate ruturning body midlleware.
> note: this is example using React as template engine.
```tsx
// server.tsx
...
import * as React from "https://jspm.dev/[email protected]";
import * as ReactDOMServer from "https://jspm.dev/[email protected]/server";@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const nums = [1, 2, 3, 4];
return (
List Nums
{nums.map(el =>{el}
)}
)
}
}class Application extends Dero {
constructor() {
super();
this.use((req, res, next) => {
// push logic and mutate body in middleware.
res.return.push((body) => {
if (React.isValidElement(body)) {
res.type("html");
return ReactDOMServer.renderToStaticMarkup(body);
}
return;
});
next();
});
this.use({ class: [UserController] });
}
}
await new Application().listen(3000, () => {
console.log("Running on port 3000")
})
...
```### All HttpResponse
```ts
class HttpResponse {
locals: any;
opts!: PondOptions;
header!: (
key?: { [k: string]: any } | string,
value?: any,
) => this | (this & Headers) | (this & string);
status!: (code?: number) => this | (this & number);
type!: (contentType: string) => this;
body!: (body?: TBody | { [k: string]: any } | null) => Promise;
json!: (body: { [k: string]: any } | null) => Promise;
file!: (
pathfile: string,
opts?: { etag?: boolean; basedir?: string },
) => Promise;
download!: (
pathfile: string,
opts?: { basedir?: string; filename?: string },
) => Promise;
redirect!: (url: string, status?: number) => Promise;
clearCookie!: (name: string) => void;
cookie!: (name: string, value: any, opts?: Cookie) => this;
view!: (
name: string,
params?: Record,
...args: any
) => Promise;
return!: ((body: any) => any)[];
}
```## Next
Next Function is a function to next step handler (on middleware).
### example next
```ts
...
.use((req, res, next) => {
res.locals = {
username: "dero"
}
next();
})
...
```## Classic
```ts
import { dero } from "https://deno.land/x/[email protected]/mod.ts";dero().get("/", (req, res) => {
res.body("Hello World");
}).listen(3000);
```## Router
Dero support classic router.
```ts
...
import { Dero, Router } from "https://deno.land/x/[email protected]/mod.ts";const app = new Dero();
const router = new Router();
router.get("/hello", (req, res) => {
res.body("hello");
})app.use({ routes: [router] }).listen(3000);
// or with middleware and prefix
// app.use({
// prefix: "/api/v1",
// wares: [midd1, midd2],
// routes: [router1, router2]
// });
...
```## listen(opts: number | object, callback?: (err?: Error, opts?: object) => void);
```ts
await app.listen(3000);
// or
const cb = (err, opts) => {
if (err) console.log(err);
console.log("Running on server " + opts?.port);
};
await app.listen(3000, cb);
// or
await app.listen({ port: 3000, hostname: "localhost" }, cb);
// or https
await app.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 app.listen({
port: 443,
certFile: "./path/to/localhost.crt",
keyFile: "./path/to/localhost.key",
alpnProtocols: ["h2", "http/1.1"],
}, cb);
```## Simple error handling.
```ts
...
// error handling
this.onError((err, req, res, next) => {
res.status(err.code || 500).body({ message: err.message });
});// not found error handling
this.on404((req, res, next) => {
res.status(404).body({ message: `Url ${req.url} not found` });
});
...
```## throw error
```ts
import {
Dero,
BaseController,
Controller,
Get,
BadRequestError
} from "https://deno.land/x/[email protected]/mod.ts";@Controller("/hello")
class HelloController extends BaseController {@Get()
hello() {
const data = findData();
if (!data) {
throw new BadRequestError("Bad data")
}
return data;
}}
...
```## Template engine
```ts
import {
BaseController,
Controller,
Dero,
Get,
} from "https://deno.land/x/[email protected]/mod.ts";import nunjucks from "https://deno.land/x/[email protected]/mod.js";
@Controller("/user")
class UserController extends BaseController {
@View("index")
@Get()
findAll() {
return {
param: "example",
};
}
}// nunjucks configure set basedir views.
nunjucks.configure("views", {/* other config */});class Application extends Dero {
constructor() {
super({
viewEngine: {
render: nunjucks.render,
},
});this.use({ class: [UserController] });
}
}// run
await new Application().listen(3000, () => {
console.log("Running on port 3000");
});
```## The role of umiddleware (.use)
```ts
// controllers or routes object { class?: Array, routes?: Array, prefix?: string, wares?: Array }
use(routerControllers: DeroRouterControllers): this;
// spread array middlewares
use(...middlewares: Array): this;
// prefix string spread array middleware (for serve static here)
use(prefix: string, ...middlewares: Array): this;
```## What Next ?
[See examples](https://github.com/herudi/dero/tree/main/examples)
## License
[MIT](LICENSE)