I saw another one of those “framework showdown” charts floating around social media this morning. You know the type. A big grid comparing Express, Koa, and Hapi, ticking boxes for performance, ecosystem size, and “ease of use.” It’s 2026, and we are still arguing about this? Apparently yes.

Honestly, it triggered me a little. Not because the comparisons are wrong—they usually get the technical specs right—but because they miss the point of why anyone actually chooses Hapi.js these days. If you’re picking a framework based on which one has the fewest lines of “Hello World” code, you’re optimizing for the wrong thing. I’ve been maintaining Node services for over a decade now, and let me tell you: I have never, not once, cared about how fast I can write the initial server file.

I care about what happens at 3 AM on a Saturday when a junior dev pushes a hotfix that bypasses authentication because they forgot to attach a middleware. That’s where Hapi shines. It’s the framework that stops you from shooting yourself in the foot, even if you’re holding the gun with both hands.

The “Configuration over Code” Philosophy (Or: Why I Love Typing)

People complain that Hapi is verbose. They aren’t wrong. It is. While Express lets you throw together a server in five lines, Hapi demands a specific structure. But here’s the thing: that verbosity is documentation. In a team of twenty developers, “implicit magic” is your enemy. You want explicit instructions.

I remember inheriting a massive Express codebase back in ’23 where the middleware chain was so long and tangled that nobody knew for sure which routes were actually protected. We had to audit every single endpoint manually. With Hapi, the configuration is right there in the route definition. You can’t miss it.

Node.js logo - Node.js Logo PNG Vector (SVG) Free Download
Node.js logo – Node.js Logo PNG Vector (SVG) Free Download

Here is what a basic, modern Hapi server setup looks like right now. Notice the async/await structure—Hapi went all-in on this years ago, and it feels incredibly natural in modern Node versions.

'use strict';

const Hapi = require('@hapi/hapi');

const init = async () => {

    // Explicit server definition. No magic listening.
    const server = Hapi.server({
        port: 3000,
        host: 'localhost',
        routes: {
            cors: true // Built-in CORS support. No extra packages needed.
        }
    });

    // Defining a route. Look at how structured this object is.
    server.route({
        method: 'GET',
        path: '/news',
        handler: async (request, h) => {
            // Simulate a DB call or async operation
            const newsItems = await getLatestNews();
            
            // "h" is the response toolkit. 
            // It gives you explicit control over the response.
            return h.response(newsItems).code(200);
        }
    });

    await server.start();
    console.log('Server running on %s', server.info.uri);
};

// Mock async function
const getLatestNews = async () => {
    return new Promise(resolve => setTimeout(() => {
        resolve([
            { id: 1, title: 'Hapi v24 Released', tags: ['tech', 'node'] },
            { id: 2, title: 'Why Express is still confusing', tags: ['opinion'] }
        ]);
    }, 100));
};

process.on('unhandledRejection', (err) => {
    console.log(err);
    process.exit(1);
});

init();

See that? It’s declarative. You define the server, you define the routes as objects, and you start it. There’s no guessing where the router logic sits. It’s boring code, and boring code is great for production.

Validation: The Superpower You Ignore

If there is one hill I will die on, it’s input validation. Most frameworks treat validation as an afterthought or a plugin you might install if you feel like it. Hapi treats it as a first-class citizen via Joi (now part of the wider ecosystem, but spiritually married to Hapi).

In 2026, security isn’t just about preventing SQL injection; it’s about ensuring your data shape is exact. If an API client sends an extra field, I want my server to scream. If a string is too long, reject it immediately. Don’t even let the handler function run.

Here is how strict Hapi gets. This saves me hours of debugging “undefined” errors deep in my business logic.

const Joi = require('joi');

server.route({
    method: 'POST',
    path: '/api/subscribe',
    options: {
        // Validation happens BEFORE the handler is called.
        // If this fails, the client gets a 400 Bad Request automatically.
        validate: {
            payload: Joi.object({
                email: Joi.string().email().required(),
                preferences: Joi.object({
                    daily: Joi.boolean().default(false),
                    topics: Joi.array().items(Joi.string()).min(1).required()
                }).required()
            })
        }
    },
    handler: async (request, h) => {
        // If we reach this line, we KNOW request.payload is valid.
        // No manual "if (!email)" checks needed here.
        const { email, preferences } = request.payload;
        
        return { status: 'subscribed', email, topics: preferences.topics };
    }
});

I’ve seen Express apps where the validation logic is mixed inside the controller, creating this spaghetti mess of if/else blocks. With Hapi, the handler function stays pure. It only deals with business logic, because the framework acted as the bouncer at the door.

Node.js logo - Green Grass, Nodejs, JavaScript, React, Mean, AngularJS, Logo ...
Node.js logo – Green Grass, Nodejs, JavaScript, React, Mean, AngularJS, Logo …

The Frontend Perspective

My frontend team actually prefers when I use Hapi. Why? Consistency. Because Hapi is so strict about errors and responses, the API behavior is predictable. They don’t get a random HTML stack trace when the server crashes; they get a proper JSON error object (unless I mess up the configuration badly).

Here is a quick example of how a frontend client consumes this. It’s standard stuff, but notice how we handle the response. Since Hapi enforces the contract, the client code can be cleaner.

// Simple DOM manipulation to consume our Hapi API
const subscribeUser = async (email, topics) => {
    const statusDiv = document.getElementById('status-message');
    
    try {
        const response = await fetch('http://localhost:3000/api/subscribe', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                email: email,
                preferences: {
                    topics: topics
                }
            })
        });

        const data = await response.json();

        if (!response.ok) {
            // Hapi sends clear validation error messages by default
            // e.g., "child \"preferences\" fails because [\"topics\" must contain at least 1 items]"
            throw new Error(data.message || 'Something went wrong');
        }

        statusDiv.innerText = Success! Subscribed ${data.email};
        statusDiv.className = 'success';

    } catch (error) {
        console.error('API Error:', error);
        statusDiv.innerText = Error: ${error.message};
        statusDiv.className = 'error';
    }
};

// Hooking it up to a button
document.getElementById('sub-btn').addEventListener('click', () => {
    subscribeUser('jane@example.com', ['tech', 'hapi']);
});

Is Hapi “Dead” in 2026?

Node.js logo - AWS Node JS MongoDB Deployment: 2 Easy Methods | Hevo
Node.js logo – AWS Node JS MongoDB Deployment: 2 Easy Methods | Hevo

Every year, someone writes a eulogy for Hapi. “It’s too heavy,” they say. “Fastify is faster,” they scream. And yeah, raw benchmarks often show other frameworks handling more requests per second. But unless you are building the next Twitter (or whatever X became), you probably aren’t bottlenecked by your HTTP router. You’re bottlenecked by your database queries or your external API calls.

The “news” isn’t about a shiny new feature that changes the paradigm. The news is that Hapi is still stable. In an ecosystem that churns through tools like fast fashion, stability is a feature. The plugin system (hpal, glue, etc.) still works the way it did three years ago. I can open a project from 2024 and it still makes sense. Can you say the same for some of those “bleeding edge” meta-frameworks?

I recently migrated a mid-sized microservice architecture from a patchwork of Express and pure Node http handlers over to Hapi. The initial setup took two days longer than estimated because of the boilerplate. But the bug reports dropped by 40% in the first month. We stopped seeing “Cannot read property of undefined” errors in the logs because the validation layer caught the bad data before it hit our logic.

So, look. If you are building a hackathon project and need to move at the speed of light, go use something minimal. But if you are building something that needs to survive for five years, take a second look at Hapi. It’s not the cool kid in the room anymore, but it’s the adult in the room. And sometimes, you really just need an adult.

By Chiamaka Ekwueme

Chiamaka is a vibrant voice in the JavaScript community, specializing in Svelte and modern web performance. She loves demystifying complex topics through engaging talks and articles, and can often be found debugging live code with a perfectly brewed cup of chai.