cd..blog

Building Scalable APIs: Hapi, Vercel, and TypeScript Integration

const published = "Feb 7, 2026, 10:36 PM";const readTime = 4 min;
hapiverceltypescriptserverlessapi
Learn to integrate Hapi.js, Vercel, and TypeScript for robust, scalable serverless APIs. This guide covers setup, best practices, and deployment.

In modern web development, combining a robust backend framework with a flexible serverless platform and strong typing offers an unparalleled developer experience. This post explores how to integrate Hapi.js, Vercel, and TypeScript to build scalable, maintainable, and high-performance applications.

Hapi.js with TypeScript: A Robust Foundation

Hapi.js [https://hapi.dev/] is a robust, configuration-centric framework for building APIs and web applications, offering a rich plugin ecosystem and strong conventions. TypeScript [https://www.typescriptlang.org/] significantly enhances Hapi development by providing static type checking, improving code quality, refactoring safety, and developer experience.

Below is a basic Hapi server setup using TypeScript. Notice the interface ServerConfig for type safety in our initialization function.

// src/server.ts
import Hapi from '@hapi/hapi';
import { Server } from '@hapi/hapi';

interface ServerConfig {
  port: number;
  host: string;
}

const init = async (config: ServerConfig): Promise<Server> => {
  const server = Hapi.server({
    port: config.port,
    host: config.host,
  });

  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      return 'Hello, Hapi with TypeScript!';
    },
  });

  server.route({
    method: 'GET',
    path: '/api/greet/{name}',
    handler: (request, h) => {
      const name = request.params.name;
      return `Greetings, ${name}!`;
    },
  });

  // Only start the server if not in a Vercel serverless environment
  if (process.env.NODE_ENV !== 'production' && config.port !== 0) {
    await server.start();
    console.log(`Server running on ${server.info.uri}`);
  }
  return server;
};

// Local development entry point
if (process.env.NODE_ENV !== 'production' && require.main === module) {
  init({ port: 3000, host: 'localhost' });
}

export { init };

Project Setup (package.json, tsconfig.json)

Ensure your package.json includes Hapi and TypeScript dependencies, along with dev tools for local development:

{
  "name": "hapi-vercel-ts",
  "version": "1.0.0",
  "main": "dist/server.js",
  "scripts": {
    "start": "ts-node src/server.ts",
    "build": "tsc",
    "dev": "ts-node-dev src/server.ts"
  },
  "dependencies": {
    "@hapi/hapi": "^21.3.2",
    "@vercel/node": "^3.0.0" 
  },
  "devDependencies": {
    "@types/hapi__hapi": "^21.0.4",
    "@types/node": "^20.12.7",
    "ts-node": "^10.9.2",
    "ts-node-dev": "^2.0.0",
    "typescript": "^5.4.5"
  }
}

A basic tsconfig.json for robust compilation:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "commonjs",
    "rootDir": ".",
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts", "api/**/*.ts"],
  "exclude": ["node_modules"]
}

Integrating Hapi with Vercel Serverless Functions

Vercel [https://vercel.com/docs] offers a cutting-edge platform for deploying serverless functions, providing automatic scaling, global distribution, and an intuitive developer experience. To deploy a Hapi application on Vercel, we must adapt it to fit Vercel's serverless function entry point, typically located in an api directory.

Create an api directory at your project's root, and within it, an index.ts file. This file will serve as the single entry point for all requests handled by Vercel's serverless function.

// api/index.ts
import { VercelRequest, VercelResponse } from '@vercel/node';
import { init } from '../src/server'; // Import your Hapi server initialization
import { Server } from '@hapi/hapi';

let hapiServer: Server | null = null;

export default async function handler(req: VercelRequest, res: VercelResponse) {
  if (!hapiServer) {
    // Initialize Hapi server once for cold starts
    hapiServer = await init({ port: 0, host: 'localhost' }); // Port 0 for ephemeral port
  }

  // Hapi's inject method allows simulating an incoming request
  const response = await hapiServer.inject({
    method: req.method || 'GET',
    url: req.url || '/',
    headers: req.headers as Record<string, string>,
    payload: req.body ? JSON.stringify(req.body) : undefined,
  });

  // Map Hapi response to Vercel response
  res.status(response.statusCode);
  for (const key in response.headers) {
    // Vercel's res.setHeader expects string or string[]
    const headerValue = response.headers[key];
    if (headerValue !== undefined) {
      res.setHeader(key, headerValue as string);
    }
  }
  res.send(response.payload);
}

In this api/index.ts handler, we import our Hapi server's init function. The hapiServer instance is cached to ensure it's initialized only once across cold starts, optimizing performance. Vercel's VercelRequest and VercelResponse types provide type safety for the incoming request and outgoing response, while server.inject() allows us to simulate an HTTP request against our Hapi server, processing it through Hapi's full routing and lifecycle.

Vercel Configuration (vercel.json)

The vercel.json file is crucial for configuring how Vercel builds and deploys your project, defining build commands, function runtimes, and routing rules. Here, we instruct Vercel to compile our TypeScript project and route all incoming HTTP requests to our serverless function.

{
  "buildCommand": "tsc --project tsconfig.json",
  "functions": {
    "api/**/*.ts": {
      "runtime": "node tolerant"
    }
  },
  "routes": [
    {
      "src": "/(.*)",
      "dest": "/api"
    }
  ]
}

The buildCommand executes tsc to compile our TypeScript source into JavaScript before deployment. The functions section specifies the Node.js runtime for our serverless API entry point. Crucially, the routes array directs all incoming traffic (/(.*)) to our /api handler, ensuring Hapi processes every request.

Deployment and Best Practices

Deployment is streamlined: simply push your code to a Git repository connected to Vercel, or use the Vercel CLI [https://vercel.com/docs/cli] with vercel deploy. For a production-ready setup, consider these best practices:

Environment Variables: Securely manage sensitive data like database credentials or API keys using Vercel's built-in environment variables, accessible via process.env in your functions.
Monorepos: For larger applications with multiple services or shared codebases, a monorepo structure (e.g., with Turborepo or Nx) can simplify dependency management and code sharing.
CI/CD Integration: Integrate Vercel deployments into your Continuous Integration/Continuous Deployment pipeline for automated testing, linting, and seamless releases, ensuring code quality and rapid iteration.

Conclusion

By combining Hapi.js's powerful API capabilities, TypeScript's type safety, and Vercel's serverless deployment model, developers can build highly scalable, performant, and maintainable backend services. This architecture provides the best of both worlds: a structured framework for complex logic and the flexibility of serverless functions.