An Interest In:
Web News this Week
- April 23, 2024
- April 22, 2024
- April 21, 2024
- April 20, 2024
- April 19, 2024
- April 18, 2024
- April 17, 2024
Meet tinyhttp, a 0-legacy, tiny and fast alternative to Express
What
tinyhttp is a modern Express-like web framework written in TypeScript and compiled to native ESM, that uses a bare minimum amount of dependencies trying to avoid legacy hell.
Here is a short list of most important features that tinyhttp has:
- 2.5x faster than Express
- Full Express middleware support
- Async middleware support
- Native ESM and CommonJS support
- No legacy dependencies, just the JavaScript itself
- Types out of the box
tinyhttp only depends on 10 packages, 3 of which are framework's internals.
tinyhttp can be used as a newer alternative to Express for your Node.js backend apps without losing the benefits of Express.
You can visit the website or a repository page to get more info, or just keep reading the article :)
Why
My initial reason to write my own web framework was that I wanted to switch from Express.
There is a list of things I don't like in Express today:
- No ESM support. Means no named imports support.
- No TypeScript support. I have to install
@types/express
- A lot of useless polyfills such as
array-flatten
orsafe-buffer
of features that are already in JavaScript / Node.js, plus use of deprecated properties - Huge and old ES5 codebase. Quite hard to read the source code (subjective / uncommon but still)
So, I started searching for alternatives of Express with the same API. The only good thing I found was Polka. It's really good but what I personally didn't like there was that it doesn't provide req
/ res
extensions out of the box and doesn't have types. So, I decided to write my own web framework.
Get started
Setup
Because tinyhttp supports and targets ESM (but supports CJS as well), let's create a module package to be able to use import
/ export
syntax.
mkdir my-cool-tinyhttp-appcd my-cool-tinyhttp-appecho '{ "type": "module" }' > package.jsontouch server.js
Note that you must have Node 13.2+ installed in order to use ES modules. If you don't want to upgrade Node.js, either use
--experimental-modules
flag or changeimport
torequire
Installation
Next we need to install tinyhttp. tinyhttp itself is split into small packages to debug / maintain things easier (for me and for a user).
All of the bare necessities to get started with tinyhttp is placed in an app
module. It contains App
, Router
, Request
and Response
interfaces to be able to do routing, middleware connection and req / res extension.
To install it, simply type:
npm i @tinyhttp/app
Because tinyhttp and middlewares for it reuse packages, it's recommended to use pnpm as a package manager, although it's not obligatory.
Hello World
Alright, we have the module with us, now let's send "Hello World" from tinyhttp:
import { App } from '@tinyhttp/app'const app = new App()const PORT = 3000app .get('/', (_, res) => void res.send('<h1>Hello World</h1>')) .listen(PORT, () => console.log(`Started on http://localhost:${PORT}!`))
This will start a web app on 3000 port. Let's check if it works by requesting it using an HTTP client (curl
or httpie
, whatever your prefer)
http localhost:3000HTTP/1.1 200 OKConnection: keep-aliveContent-Length: 20Date: Wed, 26 Aug 2020 19:56:53 GMTKeep-Alive: timeout=5etag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"<h1>Hello World</h1>
And it works :)
Main Concepts
tinyhttp inherits most of the concepts from Express so if you know Express you already know tinyhttp, but it's still worth going through some sections to know technical nuances in tinyhttp.
Routing
Now let's try to do some basic routing. As in Express, you can handle a request with any method listed in the http.METHODS
.
HTTP Methods
So, let's try to handle a POST
request:
import { App } from '@tinyhttp/app'import { once } from 'events'const app = new App()const PORT = 3000app.get('/', (req, res) => { res.send('Sent a GET!')})app.post('/', async (req, res) => { // Nothing complex here, we just listen to 'data' event and return the data as a promise to a `data` variable const data = await once(req, 'data').then(d => d.toString()) // And then we send it res.end(`Sent some data: ${data}`)})app.listen(PORT, () => console.log(`Started on http://localhost:${PORT}!`))
And in the console we try it:
echo 'tinyhttp is epic' | http POST localhost:3000HTTP/1.1 200 OKConnection: keep-aliveContent-Length: 33Date: Wed, 26 Aug 2020 20:09:15 GMTKeep-Alive: timeout=5Sent some data: tinyhttp is epic
And as you see, it works too :D. It's a very basic example and I wouldn't recommend using such request body handling, better use a body parser like Express body-parser.
Route paths and params
Also, you can use both route paths and route params, just like in Express.
Note that instead of an old
path-to-regexp
tinyhttp uses a faster and newerregexparam
module that does exactly the same (matches routes with regex) but does it more quickly.
Example with route paths:
import { App } from '@tinyhttp/app'const app = new App()const PORT = 3000const handler = (req, res) => void res.send('Hello World')app.get('/route1', handler)app.get('/route2', handler)app.listen(PORT, () => console.log(`Started on http://localhost:${PORT}!`))
Example with route params:
import { App } from '@tinyhttp/app'const app = new App()const PORT = 3000app.get('/user/:id', (req, res) => { res.send(`Hello ${req.params.id}!`)})app.listen(PORT, () => console.log(`Started on http://localhost:${PORT}!`))
And if we try to request /user/v1rtl
, we get this:
http -b localhost:3000/user/v1rtlHello v1rtl!
As I mentioned earlier, tinyhttp uses regexparam inside, so if you run into issues with something not matching when it should, go check out the author's repo.
Middlewares
As in any backend application, we should be able to use middlewares, both external and custom ones. Because tinyhttp's Request
and Response
interfaces extend built-in IncomingMessage
and ServerResponse
, everything is compatible with Express middlewares, including typings (at least I haven't run into issues with using express typescript wares).
Custom wares
But before trying Express middleware, let's first create a custom one and try to attach it to an app. We'll write a very simple logger that will send messages to console once a request gets finished:
import { App } from '@tinyhttp/app'const app = new App()const PORT = 3000function logger(req, res, next) { res.on('finish', () => console.log(`${req.method} ${req.url} ${res.statusCode}`)) next()}app .use(logger) .get((req, res) => res.send('Hello World'))app.listen(PORT, () => console.log(`Started on http://localhost:${PORT}!`))
As you see, a middleware uses next
function. It's required for tinyhttp to understand that we finished doing things in this function and that we need to switch to the next middleware.
Now let's launch our Node.js application and do some random requests at the same time:
http -b localhost:3000Hello World http POST localhost:3000 -bNot Found http -b localhost:3000/aaaNot Found
And in the logs we'll see this:
node index.jsStarted on http://localhost:3000!GET / 200POST / 404GET /aaa 404
Our logger works as expected, yay!
By the way, if you're looking for a good, yet simple logger, you can try our own @tinyhttp/logger
. Although, any logger for Express (and HTTP apps in general, if they use (req, res, next
) for wares) will work.
Express wares
So, now let's try to use some express-specific middleware, for instance express-graphql
:
npm i graphql express-graphql
import { App } from '@tinyhttp/app'import graphql from 'graphql'import expressGraphQL from 'express-graphql'const app = new App()const port = parseInt(process.env.PORT) || 3000const schema = graphql.buildSchema(` type Query { hello: String }`)const rootValue = { hello: () => 'Hello world!',}app.use( '/graphql', expressGraphQL.graphqlHTTP({ schema, graphiql: { headerEditorEnabled: true }, rootValue, }))app.listen(port, () => console.log(`Listening on http://localhost:${port}`))
And if we try to query hello
, we'll get Hello world!
as expected:
curl 'http://localhost:3000/graphql?query=%7B%0A%20%20hello%20%20%0A%7D%0A'{ "data": { "hello": "Hello world!" }}
Async wares and error handling
You may noticed previously that we used async
for a middleware handler function. tinyhttp supports async handlers, but keep in mind that you better wrap them in try...catch
to prevent hanging Unhandled Promise Rejection
errors. You can use next
function to pass errors to a generic error handler (which is defined in App
constructor, see docs for more info)
import { App } from '@tinyhttp/app'import { promises as fs } from 'fs'const app = new App()const PORT = 3000app .use(async (req, res, next) => { let file try { file = await fs.readFile('express 2020.txt') res.send(file.toString()) } catch (e) { console.log(`Failed to open ${e.path} file`) next(e) } })app.listen(PORT, () => console.log(`Started on http://localhost:${PORT}!`))
If we try to open a file with making a request to /
(without assuming you created it) we'll get a 500 server error with this message in the console:
http localhost:3000HTTP/1.1 500 Internal Server ErrorConnection: keep-aliveContent-Length: 21Date: Wed, 26 Aug 2020 20:55:35 GMTKeep-Alive: timeout=5Internal Server Error
// node server.jsFailed to open express 2020.txt file
Request / Response extensions
As you remember, Express contains a lot of helper functions in their req
a res
objects. tinyhttp has them too and is in progress of transferring all of them, but at the moment the most common is already here (for detailed descriptions with examples see docs).
Here's a list of what is present in Request
:
- req.hostname
- req.query
- req.route
- req.params
- req.protocol
- req.secure
- req.xhr
- req.fresh
- req.stale
- req.accepts
- req.get
and in Response
:
- res.cookie
- res.clearCookie
- res.end
- res.json
- res.send
- res.status
- res.set
- res.links
- res.location
I recommend to go through documentation to read more about the extensions you want to use in tinyhttp, but most of them works identically to Express ones.
aaaannd that's all you need to know about tinyhttp!
Conclusion
You can start using it today for backend applications, the repository contains a lot of examples, including MongoDB and GraphQL integrations.
Plans for the future
I'm thinking of keeping working on the framework until 1.0 release. By now, I have to cover all of the codebase in tests (atm only 47% is covered), make more examples and implement all of the middlewares from the list to become independent from Express ecosystem.
Feedback
Let me know what you think about tinyhttp! If you decided to try it, but something didn't work as it should, please consider opening an issue!
Thanks for reading this article :)
and please don't forget to star tinyhttp on github!
Original Link: https://dev.to/talentlessguy/meet-tinyhttp-a-0-legacy-tiny-and-fast-alternative-to-express-4m92
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To