NHttp
Just native HTTP/2 micro framework for Deno. so hot 🚀
Note: Deno native HTTP/2 Hyper requires Deno version 1.9.0 or higher.
Features
- HTTP/2 support.
- Middleware support.
- Sub router support.
- Includes body parser (jsonBody, urlencodedBody).
- No third party modules and no std/lib by default.
Benchmark
Here the simple benchmark.
autocannon -c 100 http://localhost:3000/hello
Name | Req/sec | Throughput |
---|---|---|
Native | 21433 | 2.5 MB |
NHttp | 21127 | 2.5 MB |
std/http | 14569 | 626 KB |
Note: maybe not relevant if compared with std/http or other Deno framework using std/http. nhttp uses native deno http.
Installation
deno.land
import { NHttp } from "https://deno.land/x/nhttp@0.2.8/mod.ts";
nest.land
import { NHttp } from "https://x.nest.land/nhttp@0.2.8/mod.ts";
Usage
import { NHttp } from "https://deno.land/x/nhttp@0.2.8/mod.ts";
const app = new NHttp();
// rev is RequestEvent
app.get("/hello", (rev) => {
rev.response.send('Hello');
});
app.get("/json", ({ response }) => {
response.json({ name: 'nhttp' });
});
app.post("/save", ({ response, body }) => {
response.json(body);
});
app.listen(3000, () => {
console.log("> Running on port 3000");
});
Run
Note: Deno native http is unstable. so just add –unstable flag.
deno run --allow-net --allow-read --unstable yourfile.ts
Middleware
import { NHttp, Handler } from "https://deno.land/x/nhttp@0.2.8/mod.ts";
const app = new NHttp();
const foo: Handler = (rev, next) => {
rev.foo = "foo";
next();
}
app.use(foo);
app.get("/foo", ({ response, foo }) => {
response.send(foo)
});
app.listen(3000);
Wrapper for middleware
Simple wrapper like HOC for middleware (req, res, next);
Note: not all middleware can work.
import { NHttp, Handler, wrapMiddleware, UnprocessableEntityError } from "https://deno.land/x/nhttp@0.2.8/mod.ts";
import { body, validationResult } from "https://esm.sh/express-validator";
const app = new NHttp();
// example express validator
const validator: Handler[] = [
wrapMiddleware([
body("username").isString(),
body("password").isLength({ min: 6 }),
body("email").isEmail(),
]),
(rev, next) => {
const errors = validationResult(rev);
if (!errors.isEmpty()) {
// status 422
throw new UnprocessableEntityError(errors.array());
}
next();
},
];
app.post("/user", validator, ({ response, body }) => {
response.send(body)
});
app.listen(3000);
wrapMiddleware(…middleware: any, { beforeWrap });
BeforeWrap
Mutate RequestEvent and HttpResponse before wrap middleware.
...
app.use(wrapMiddleware(
[midd1(), midd2()],
{
beforeWrap: (rev, res) => {
// rev.any = fn;
// res.any = fn;
}
}
))
...
Router
import { NHttp, Router } from "https://deno.land/x/nhttp@0.2.8/mod.ts";
const app = new NHttp();
// user router
const userRouter = new Router();
userRouter.get("/user", ({ response }) => {
response.send("hello user");
});
// item router
const itemRouter = new Router();
itemRouter.get("/item", ({ response }) => {
response.send("hello item");
});
// register the router
app.use('/api', [userRouter, itemRouter]);
// or with middleware
// app.use('/api', mid1, mid2, [userRouter, itemRouter]);
app.listen(3000);
Rev
rev is a RequestEvent with some object like :
// default from Deno.RequestEvent
request: Request;
respondWith(r: Response | Promise<Response>): Promise<void>;
// custom
body: { [k: string]: any };
file: { [k: string]: any };
responseInit: ResponseInit;
response: HttpResponse;
url: string;
originalUrl: string;
params: { [k: string]: any };
path: string;
query: { [k: string]: any };
search: string | null;
_parsedUrl: { [k: string]: any };
getCookies: (decode?: boolean) => Record<string, string>;
// more...
Request
Just Web API Request.
...
console.log(rev.request.method);
// => GET
console.log(rev.request.url);
// => http://localhost:3000/path
console.log(new URL(rev.request.url));
// => URL {...}
console.log(rev.request.headers);
// => Headers {...}
...
Note: rev.request.url is a full url. rev.url is a path url.
Response
...
// example with status and headers
app.get("/hello", ({ response }) => {
response.status(200).header({ 'name': 'value' }).send('hello');
})
...
Response.header
header: (key?: object | string | undefined, value?: any) => HttpResponse | string | Headers;
...
// key and value
res.header("key1", "value1");
// with object
res.header({ "key2": "value2" });
// multiple header
res.header({
"key3": "value3",
"key4": "value4"
});
// get header
console.log(res.header());
// => Headers {
// "key1":"value1",
// "key2":"value2",
// "key3":"value3",
// "key4":"value4",
// }
// get header by key
console.log(res.header("key1"));
// => value1
// delete key1
res.header().delete("key1");
console.log(res.header());
// => Headers {
// "key2":"value2",
// "key3":"value3",
// "key4":"value4",
// }
// convert to json object
console.log(Object.fromEntries(res.header().entries()));
// => {
// "key2":"value2",
// "key3":"value3",
// "key4":"value4",
// }
// reset header
res.header(new Headers());
console.log(res.header());
// => Headers { }
Response.type
Shorthand for response.header(“Content-Type”, yourContentType);
Response.status
status: (code?: number | undefined) => HttpResponse | number;
// set status
response.status(201);
// get status
console.log(response.status());
// => 201
Response.send
send: (body?: BodyInit | { [k: string]: any } | null) => Promise;
Response.json
json: (body: { [k: string]: any } | null) => Promise;
Response.redirect
redirect: (path: string, status?: number) => Promise;
Response.cookie
...
response.cookie("key", "value", {
HttpOnly: true,
maxAge: 60 * 60,
// encode value
encode: true
})
...
Type Cookie
type Cookie = {
expires?: Date;
maxAge?: number;
domain?: string;
path?: string;
secure?: boolean;
httpOnly?: boolean;
sameSite?: "Strict" | "Lax" | "None";
other?: string[];
encode?: boolean;
};
Request getCookies
...
rev.getCookies();
// decode if encode true
rev.getCookies(true);
...
RespondWith
Just callback Web API Response.
...
// example with status and headers
app.get("/hello", (rev) => {
rev.respondWith(new Response("Hello", {
status: 200,
headers: new Headers({
'x-powered-by': 'nhttp'
})
}))
})
...
Upload File
import { NHttp, multipart } from "https://deno.land/x/nhttp@0.2.8/mod.ts";
const app = new NHttp();
// handle upload multipart/form-data
app.post("/upload", multipart.upload({ name: "image" }), ({ response, file }) => {
console.log(file.image);
// => file or [file1, file2]
response.send('success upload file');
});
app.listen(3000);
Multipart
...
// non upload
multipart.default();
// upload
multipart.upload({ name: "image" });
// single upload
multipart.upload({ name: "image", maxCount: 1 });
// required field (will throw bad request error 400)
multipart.upload({ name: "image", required: true });
// accept file
multipart.upload({ name: "image", accept: 'png|jpg' });
// maxSize file
multipart.upload({ name: "image", maxSize: '2 mb' });
// callback
multipart.upload({
name: "image",
callback: (file) => {
// change filename
file.filename = Date.now() + file.name;
}
});
// destination
multipart.upload({
name: "image",
dest: "public/user_folder/"
});
// multiple field
multipart.upload(
[
{
name: "user_image",
dest: "public/user_folder/"
// other
},
{
name: "employee_image",
dest: "public/employee_folder/"
// other
}
]
);
...
Params
Just object path parameters.
...
app.get("/user/:name", ({ response, params }) => {
console.log(params);
// => { name: 'john' }
response.send(params.name);
});
...
// optional params.
// => { name: 'john' } or { name: null }
app.get("/user/:name?", ...handlers);
// extension params (Example: only png and jpg).
// => { filename: 'file.jpg' } or { filename: 'file.png' }
app.get("/image/:filename.(png|jpg)", ...handlers);
// exact params.
// => { wild: ['param1', 'param2'] }
app.get("/all/*", ...handlers);
Query
Just object query parameters.
...
// example: /user?name=john
app.get("/user", ({ response, query }) => {
console.log(query);
// => { name: 'john' }
response.send(query.name);
});
...
Error Handling
...
// global error handling
app.onError((error, rev) => {
rev.response.status(error.status || 500).send(error.message);
})
// global not found error handling
app.on404((rev) => {
rev.response.status(404).send('Not Found');
})
...
Error
Simple error handling.
import { NHttp, BadRequestError } from "https://deno.land/x/nhttp@0.2.8/mod.ts";
const app = new NHttp();
app.post("/user", ({ response }) => {
const data = await saveUser();
if (!data) {
throw new BadRequestError('Bad request for save');
}
response.send(data);
});
app.listen(3000);
Listen
app.listen(3000);
// or
const callback = (err, opts) => {
if (err) console.log(err);
console.log("Running on server 3000");
}
app.listen(3000, callback);
// or
app.listen({ port: 3000, hostname: 'localhost' }, callback);
// or https
app.listen({
port: 443,
certFile: "./path/to/localhost.crt",
keyFile: "./path/to/localhost.key",
}, callback);
// or http/2
app.listen({
port: 443,
certFile: "./path/to/localhost.crt",
keyFile: "./path/to/localhost.key",
alpnProtocols: ["h2", "http/1.1"]
}, callback);
Config
...
// example config
const app = new NHttp({
parseQuery: qs.parse, /* default from utils */
bodyLimit: {
json: "1mb", /* default 3mb */
urlencoded: "1mb" /* default 3mb */
},
env: "development" /* or production */
});
...
What’s Next ?
Want to contribute to this project? I gladly welcome it.
- Please fork.
- Create a branch.
- Commit changes (before commit, please format the code with the command
deno fmt
in the src folder). - Push to the created branch.
- Make a PR (Pull Requests).
- Thanks.
List
- Server App
- Middleware
- Router
- Body Parser
- Examples
- Doc
- Unit Test
- Coverage