DEV Community

Cover image for Node.js Frameworks Roundup 2024 — Elysia / Hono / Nest / Encore — Which should you pick?
Simon Johansson for Encore

Posted on

Node.js Frameworks Roundup 2024 — Elysia / Hono / Nest / Encore — Which should you pick?

Node.js web frameworks — where do we even begin? With so many options out there, choosing the right one for your project can feel overwhelming.

In this post, I’ll walk you through the hottest frameworks in the Node.js ecosystem, breaking down the strengths, weaknesses, and best use cases for each one.

Whether you’re looking for speed, scalability, or simplicity, hopefully we will cover it all—so by the end, you’ll know exactly which framework is right for you.

The frameworks we will be looking at are: Nest, Elysia, Encore.ts and Hono.

Video version:

Here we go!

Feature overview

On a scale from most lightweight to most feature rich I would position the frameworks like this:

feature overview

This does not mean that lightweight is bad, it just depends on what the needs are for your project. The lightweight nature of Hono is actually one of the selling points, with just under 14KB its perfect for deploying to Cloudflare Workers.

Encore.ts on the other hand comes with a lot of built in features, like automatic tracing out of the box and local infrastructure.

Let’s take a look at each framework, and we are going to start with Encore.ts

Encore.ts

An Open Source framework that aims to make it easier to build robust and type-safe backends with TypeScript. The framework has a lot of build in tools to make your development experience smoother and performance wise, it’s the fasters of all frameworks in this comparison.

Encore has built-in request validation. The request and response types you define in regular TypeScript are used to validate the request, both during compile and runtime. And unlike the other frameworks the actual validation is done in Rust and not in JavaScript. This makes the validation really fast, but more on that later.

import {api, Header, Query} from "encore.dev/api";

enum EnumType {
  FOO = "foo",
  BAR = "bar",
}

// Encore.ts automatically validates the request schema 
// and returns and error if the request does not match.
interface RequestSchema {
  foo: Header<"x-foo">;
  name?: Query<string>;

  someKey?: string;
  someOtherKey?: number;
  requiredKey: number[];
  nullableKey?: number | null;
  multipleTypesKey?: boolean | number;
  enumKey?: EnumType;
}

// Validate a request
export const schema = api(
  {expose: true, method: "POST", path: "/validate"},
  (data: RequestSchema): { message: string } => {
    console.log(data);
    return {message: "Validation succeeded"};
  },
);

Enter fullscreen mode Exit fullscreen mode

Encore makes it easy to create and call services. From a code perspective, a service just looks like another folder in your repo. When calling an endpoint in a service it’s just like calling a regular function. But the cool part is that under the hood, those function calls gets converted to actual HTTP calls. When deploying, you can even choose to deploy your services to separate instances, for example in a Kubernetes cluster, while still having all service code in the same repo.

Import a service and call its API endpoints like regular functions

import { api } from "encore.dev/api";
import { hello } from "~encore/clients"; // import 'hello' service

export const myOtherAPI = api({}, async (): Promise<void> => {
  // all the ping endpoint using a function call
  const resp = await hello.ping({ name: "World" });
  console.log(resp.message); // "Hello World!"
});
Enter fullscreen mode Exit fullscreen mode

With Encore.ts you integrate your infrastructure as type-safe objects in application code. Creating a database or a Pub/Sub topic just requires a few lines of application code. Encore.ts builds your application as a Docker image and you just supply the runtime configuration when deploying, that’s it.

Create a PostgreSQL database in one line of code

import { SQLDatabase } from "encore.dev/storage/sqldb";

const db = new SQLDatabase("userdb", {migrations: "./migrations"});
// ... use db.query to query the database.
Enter fullscreen mode Exit fullscreen mode

Create Pub/Sub topics and subscriptions

import { Topic } from "encore.dev/pubsub";

export interface User { /* fields ... */ }

const signups = new Topic<User>("signup", {
  deliveryGuarantee: "at-least-once",
});

await signups.publish({ ... });
Enter fullscreen mode Exit fullscreen mode

Encore also comes with a built-in development dashboard. When you start your Encore app, the development dashboard is available on port localhost:9400. From here you can call your endpoints, a bit like Postman. Each call to your application results in a trace that you can inspect to see the API requests, database calls, and Pub/Sub messages. The local development dashboard also includes automatic API documentation and an always up-to-date architectural diagram of you system.

Local Development Dashboard

And its worth mentioning that even though Encore comes with a lot of features, it has 0 npm dependencies.

Hono

Hono is the creation of Yusuke Wada. He started the project 2021 because there were no good Node.js frameworks that worked well on Cloudflare Workers. Since then Hono as added support for a lot of other runtimes like Node.js, Bun and Deno.

import { Hono } from 'hono'
const app = new Hono()

app.get('/', (c) => c.text('Hono!'))

export default app
Enter fullscreen mode Exit fullscreen mode

Hono is really small, the hono/tiny preset is under 13kB which makes it really suitable for deploying to Cloudflare Workers. Hono also has 0 NPM dependencies which is really impressive!

The multi-runtime selling point is interesting, the idea that no matter the Javascript runtime you will be able to run Hono. In their repo they have this concept of adapters where you can see the adjustments they make for each runtime. I think this has been huge for adoption and growing the user base but realistically for an individual user you will probably not switch runtimes once you have your app deployed to the cloud.

And even though Hono is lightweight out of the box it has a bunch of middleware, both 1st and 3rd-party that you can install to enhance your app. It’s the “add it when you want to use it”-approach that got popular with Express. This can work great as long as you app is small but with a larger app, maintaining a huge list of dependencies can be frustrating.

Elysia

Elysia, like Encore, is built for TypeScript and also offers type-safety inside your API handlers. Knowing what you are working with inside your API handlers saves you a lot of time and it’s great to not having to litter your code with type checks.

You specify the request types with the t module, which is an extension of the TypeBox validation library. Unlike Encore, the validation happens in the JavaScript layer which adds some performance overhead.

import { Elysia, t } from 'elysia'

new Elysia()
  .patch("/profile", ({ body }) => body.profile, {
    body: t.Object({
      id: t.Number(),
      profile: t.File({ type: "image" }),
    }),
  })
  .listen(3000);
Enter fullscreen mode Exit fullscreen mode

Adding Swagger documentation only requires one line of code and Elysia has 1st party support for OpenTelemetry. Which is really nice, so you can easily monitor your app regardless of the platform.

Elysia is fast! But not as fast as Encore as you will see in the next section.

Nest.js

Nest.js is a bit different then the other frameworks in this comparison. Encore, Elysia and Hono tries to offer minimalistic APIs for creating endpoints and middleware and you are free to structure your business logic however you wish. Nest.js is much more opinionated and forces you to structure your code in a certain way. It offers a modular architecture that organizes code into different abstractions, like Providers, Controllers, Modules and Middleware.

Nest is designed to make it easier to maintain and develop larger applications. But whether your a fan of the opinionated structure Nest offers is at the end of the day very subjective. I would say that it might be advantageous for large-scale projects where long-term maintainability are more important than speed and simplicity. For smaller project with just a few developers the added level of abstractions will probably be overkill for most use cases. The opinionated nature of Nest also brings with it a steeper learning curve compared to the Hono, Encore and Elysia.

hard to learn

When using Nest you can choose to either use Express or Fastify as the underlying HTTP server framework. All the Nest functionality is added on top of that.

Performance

Speed is perhaps not the most important thing when choosing a framework, but it’s not something you can ignore. It will impact your apps responsiveness AND ultimately your hosting bill.

speed

We have benchmarked both without and with request schema validation, the measurement is requests per second. The names in parenthesis are the request validation libraries used. Encore.ts has request validation built in.

benchmark

Encore.ts is the fastest for all frameworks in our benchmark followed by Elysia, Hono, Fastify and Express. Nest uses Fastify or Express under the hood so you can expect the performance to for a Nest app to be equivalent to that, maybe a but slower as Nest adds some overhead.

How can Encore.ts be so much faster? The secret is that Encore.ts has a Rust runtime. And Rust is fast!

Encore.ts actually consists of two parts:

  1. A user facing TypeScript part for defining APIs and infrastructure.

  2. And under the hood, it has a multi-threaded runtime written in Rust.

The key to the performance boost is to off-load as much work as possible from the single-threaded Node.js event-loop to the Rust runtime.

For example, the Rust runtime handles all input/output like accepting incoming HTTP requests or reading from the database. Once the request or database query has been fully processed, then it gets handed over to the Node.js event-loop.

Code structure

Hono, Elysia and Encore are unopinionated in how you structure your code. And the way you create APIs and middleware are fairly similar between the frameworks.

Here is a GET endpoint for each framework. There are of course some differences, but if we squint the APIs look fairly similar. At least similar enough for this not to be the deciding factor in my opinion:

Encore.ts

interface Response {
  message: string;
}

export const get = api(
  { expose: true, method: "GET", path: "/hello/:name" },
  async ({ name }: { name: string }): Promise<Response> => {
    const msg = `Hello ${name}!`;
    return { message: msg };
  },
);
Enter fullscreen mode Exit fullscreen mode

Elysia

import { Elysia, t } from "elysia";

new Elysia()
  .get(
    "/hello/:name",
    ({ params }) => {
      const msg = `Hello ${params.name}!`;
      return { message: msg };
    },
    {
      response: t.Object({
        message: t.String(),
      }),
    },
  )
Enter fullscreen mode Exit fullscreen mode

Hono

import { Hono } from "hono";

const app = new Hono();

app.get("/hello/:name", async (c) => {
  const msg = `Hello ${c.req.param("name")}!`;
  return c.json({ message: msg });
});
Enter fullscreen mode Exit fullscreen mode

What really makes a difference is the being able to relay on type-safety when building a robust application. Encore and Elysia offer type-safe APIs but Encore also offers compile time type-safety when working with infrastructure like Pub/Sub. With Encore, you also get compile time type-safety when calling an endpoint in another service. And if you've ever worked with a microservice architecture before you know how big of a deal that is.

type safe

Nest.js is the one that really sticks out in terms of API design. There are a lot of concepts and abstractions that goes into a Nest app. This can be both a good and a bad thing, it really depends of your preference. One thing that will be immediately apparent when looking at a Nest app is the use of decorators. Nest relies heavily on decorators, for example when using dependency injection to inject a Services into Controllers.

Nest controller

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
Enter fullscreen mode Exit fullscreen mode

Personally, I am not a big fan and I know that I am not the only one out there.

Deployment & Infrastructure

All of these frameworks are similar in that they are deployable to to all mainstream cloud platforms (like Digital Ocean and Fly.io) or straight to a cloud provider like AWS or GCP.

Encore offers automatic local infrastructure. encore run starts your app and spinns up all local infrastructure, like databases and Pub/Sub topic. Forget YAML, Docker Compose, and the usual headaches.

encore run

When building your Encore app you get a runtime config where you can supply the configuration needed for connecting to the infrastructure in your cloud.

If you want to get your Encore app into the cloud quickly and don’t feel like self-serving then you can use Encore Cloud. Encore Cloud offers CI/CD and preview environments for pull requests. And if you want to, Encore Cloud can provision all the needed infrastructure in your own cloud on AWS or GCP. This means your app is not dependent on any third-party services and you have full control over all your infrastructure.

Hono sticks out in that it supports a bunch of different runtimes, you therefore have a lot of options when it comes to deployment. Deploying to Cloudflare Workers, Netlify or AWS Lambda is easy and does not require a lot of configuration.

With Nest you run the nest build command that compiles your TypeScript code into JavaScript. This process generates a dist directory containing the compiled files. Thats pretty much it, you can then use Node.js to run your dist folder.

The recommended way to deploy an Elysia application is to compile your app into a binary using the bun build command. Once binary is compiled, you don't need Bun installed on the machine to run the server.

Wrapping up

That’s it! Hopefully you know what framework to reach for in your next project.

If you want to learn more about Encore.ts, you can check out the Open Source project on GitHub: https://github.com/encoredev/encore

Recommendations:

Top comments (26)

Collapse
 
mindplay profile image
Rasmus Schultz

Encore is presented as a framework here, which is actually misleading - it is an alternative runtime that replaces Node entirely.

Encore is an entirely different category from the things you're comparing to here. This wasn't even touched on in this article.

To be honest, the whole article reads like another attempt at misleading advertisement for Encore, and it isn't the first time you've attempted something like that:

dev.to/encore/how-to-make-your-exp...

A similarly misleading title (which you declined to change) and essentially a guide to porting your Express app to Encore.

Likewise, this article would have you believe we're comparing frameworks, when Encore isn't even strictly (or at least not only) a framework.

I honestly think you're going about your marketing and placement of Encore all wrong.

Why are you trying to position this as an alternative to Express, one of the lightest lightweight frameworks there is?

You're an entirely different category of product.

I'm not honestly completely sure what category of product you are. You want to be a Typescript framework, but you're really a platform, and you're based on Go?

To be honest, this framework seems to be trying to break out of it's own niche, perhaps because it's a niche that doesn't really exist? 🤨

Collapse
 
marcuskohlberg profile image
Marcus Kohlberg • Edited

Encore.ts does not replace Node, it uses the Node runtime.
In most common uses of the phrase, it is a backend framework.
It also provides an extension to the javascript runtime (Node) by providing a Rust runtime to handle I/O.

Encore.go is a separate framework for Go.

Both can, if you want, be used with Encore's Cloud Platform to automate DevOps.
If you prefer, you can also self-host any way you like only using the Open Source tooling, you don't need an account with Encore or anything like that. So to be clear, the "platform" you a refer to is an optional related piece of kit.

I don't understand why you think it's a bad thing to compare it to frameworks like Express, even if they indeed are quite different you can replace Express with Encore.ts, and not use the other functionality it provides. The fact that there are more features available - if you want to use them - doesn't make it a terrible comparison imo. Only comparing extremely similar things doesn't seem very useful?

Collapse
 
mindplay profile image
Rasmus Schultz

Huh. So, do you mean, it has a bundled copy of Node that it uses internally?

Or how come you have to use a custom tool and a custom executable to install and host it?

Why isn't this a regular NPM package?

Thread Thread
 
eandre profile image
André Eriksson

No, it uses the node binary on your PATH, like anything else. The reason for the tooling surrounding this is to provide a nice developer experience, by handling things like automatically setting up local infrastructure. At the end of the day running it is as simple as invoking node and pointing it at the Encore-generated entrypoint file, exactly like how e.g. Next.js works.

Collapse
 
ibnu_rasikh profile image
Ibnu Rasikh • Edited

but in the docs they say "100% compatibility with the Node.js ecosystem." is it lie?
encore.dev/docs/ts/concepts/benefi...

Collapse
 
eandre profile image
André Eriksson • Edited

It's not a lie; Rasmus Schulz is mistaken. Encore.ts provides a native library (the Rust runtime) which runs within a normal Node.js process, which is how it provides 100% compatibility with the Node.js ecosystem.

Collapse
 
mage1k99 profile image
Magesh Babu

It's good to see someone has good clarity about these things

Collapse
 
eshimischi profile image
eshimischi

And also Elysia isn’t even NodeJS framework too.. based on Bun.

Collapse
 
eshimischi profile image
eshimischi

Thanks for making this statement, i thought to post something already and can’t say it better.

Collapse
 
codexam profile image
Subham • Edited

Honestly, NestJS is the way to go. Once you've tried it, you realize it’s basically like the TypeScript equivalent of Spring Boot but with a lot less headache. Nest is made for scaling, and if you're dealing with microservices or even just a well-organized architecture, it's a no-brainer. Other frameworks? Setting up pipelines, decorators, handling Redis, Kafka, RabbitMQ, gRPC – you’ll be building all that from scratch and probably still not hit the same efficiency.

With Nest, everything's already encapsulated and streamlined through decorators and built-in setups. Even on smaller projects, you’re building faster because you’ve got all these structured modules, and you’re not having to reinvent the wheel. The way it separates business logic, services, and controllers keeps your codebase manageable – you won’t end up with a pile of spaghetti code. Trust me, after trying all kinds of frameworks, Nest just hits different for clean, scalable builds.

You can check one of my blogs about nest - Scalable REST APIs with NestJS

Collapse
 
sackingsand profile image
Yudha Putera Primantika

NestJs does come with significant learning curve compared to others, but later when you really understood what it's opinions are, you'll be flying with your coding and fixing. That thing is crazy good for microservice infrastructure, it's very intuitive and saves lots of time.

Collapse
 
ozzythegiant profile image
Oziel Perez

It's really cool how it's basically Angular for backend

Collapse
 
fasttrackrai profile image
FastTrackr AI

Nice

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
simonjohansson profile image
Simon Johansson

Encore is both a Go and TypeScript framework. If you choose to use Encore with TypeScript you can use it like any other Node framework, works with all NPM libraries as usual etc.
encore.dev/

Collapse
 
Sloan, the sloth mascot
Comment deleted
 
eandre profile image
André Eriksson

You seem confused. Encore.ts is a Node.js framework. It provides a native library (built in Rust) which runs within a normal Node.JS process.

Collapse
 
usman_awan profile image
USMAN AWAN

Bro, nice work. But in 2025 deno might overcome NodeJs soon.

Collapse
 
eshimischi profile image
eshimischi

Bun already did overcome both of them

Collapse
 
naynaingoo2003 profile image
naynaingoo2003

Time to code

Collapse
 
sixman9 profile image
Richard Joseph

No Remix, deploys everywhere, including Cloudflare?

Collapse
 
kokkondaabhilash profile image
Abhilash

Why not ExpressJS added here?

Collapse
 
fredericrous profile image
Frederic R.

you should peak Effect

Some comments may only be visible to logged-in visitors. Sign in to view all comments.