Actually, I saw another one of those “Top 10 Node.js Frameworks” lists floating around Twitter yesterday. You know the type — they list Express (which is basically a museum exhibit at this point), then throw in whatever flavor-of-the-month micro-framework is trending on GitHub, and maybe, if we’re lucky, Hapi gets a pity mention at number seven. It drives me nuts.

Well, that’s not entirely accurate. Here we are in 2026, and while everyone is chasing the latest edge-runtime-compatible, zero-config, AI-generated backend solution, I’m over here deploying Hapi.js. Again. And I’m sleeping like a baby because the thing just works. The team released v24.1.0 back in January, and honestly? It’s the most “boring” release I’ve seen in years. That is a massive compliment.

Stability isn’t sexy, but it pays the bills

I remember fighting with the early versions of Fastify and NestJS when they were breaking APIs every other Tuesday. Hapi has never done that to me. The philosophy hasn’t shifted: configuration over code, explicit over implicit.

The big news with the v24 cycle isn’t some “game-changing” AI integration. It’s that they finally, finally smoothed out the rough edges with native ESM (ECMAScript Modules) and top-level await support in the plugin ecosystem. But if you tried to mix CommonJS and ESM plugins in Hapi back in 2024, you know the pain. The dreaded ERR_REQUIRE_ESM haunted my dreams for months.

I tested the new upgrade path on a legacy client project last Tuesday — running Node 24.2.0 on my Linux workstation — and it was shockingly uneventful. No cryptic stack traces. It just booted.

Hapi.js logo - Node.js for Backend Development in 2024 - Aimprosoft
Hapi.js logo – Node.js for Backend Development in 2024 – Aimprosoft

The Code: Still Verbose, Still Safe

Critics always complain that Hapi is verbose. They aren’t wrong. You have to type more. But look at what you get for that extra typing: readability that doesn’t degrade six months later when you’ve forgotten how the app works.

import Hapi from '@hapi/hapi';
import Joi from 'joi';

const init = async () => {

    const server = Hapi.server({
        port: 3000,
        host: 'localhost',
        debug: { request: ['error'] } // Built-in logging is still a lifesaver
    });

    server.route({
        method: 'POST',
        path: '/news/subscribe',
        options: {
            validate: {
                payload: Joi.object({
                    email: Joi.string().email().required(),
                    preferences: Joi.array().items(Joi.string()).min(1)
                }),
                failAction: (request, h, err) => {
                    // This lifecycle hook is where Hapi shines
                    console.error('Validation failed:', err.message);
                    throw err; 
                }
            }
        },
        handler: async (request, h) => {
            // If we get here, we KNOW the data is valid.
            const { email } = request.payload;
            
            // Simulate async db        
        

By Akari Sato

Akari thrives on optimizing React applications, often finding elegant solutions to complex rendering challenges that others miss. She firmly believes that perfect component reusability is an achievable dream and has an extensive collection of quirky mechanical keyboard keycaps.