Deploying a Hapi App
Hapi is a powerful, enterprise-grade web framework for Node.js that emphasizes security, reliability, and developer experience. With its comprehensive plugin system, built-in request validation, authentication mechanisms, and flexible routing, Hapi is the ideal choice for building robust APIs, microservices, and web applications that demand high standards of code quality and maintainability. Organizations worldwide rely on Hapi for mission-critical applications.
This comprehensive guide walks you through deploying a Hapi application to Klutch.sh, covering both automatic Nixpacks-based deployments and Docker-based deployments. You’ll learn installation steps, explore sample code, configure environment variables, and discover best practices for production deployments.
Table of Contents
- Prerequisites
- Getting Started: Install Hapi
- Sample Code Examples
- Project Structure
- Deploying Without a Dockerfile (Nixpacks)
- Deploying With a Dockerfile
- Environment Variables & Configuration
- Plugins & Performance Optimization
- Troubleshooting
- Resources
Prerequisites
To deploy a Hapi application on Klutch.sh, ensure you have:
- Node.js 18 or higher - Hapi requires a modern Node.js version for optimal performance
- npm or yarn - For managing dependencies
- Git - For version control
- GitHub account - Klutch.sh integrates with GitHub for continuous deployments
- Klutch.sh account - Sign up for free
Getting Started: Install Hapi
Create a New Hapi Project
Follow these steps to create and set up a new Hapi application:
-
Create a new directory for your project and initialize npm:
Terminal window mkdir my-hapi-appcd my-hapi-appnpm init -y -
Install Hapi and development dependencies:
Terminal window npm install @hapi/hapinpm install --save-dev nodemonThe
nodemonpackage helps with development by automatically restarting your server when files change. -
Create a basic Hapi server. Create a file called `index.js`:
const Hapi = require('@hapi/hapi');const port = process.env.PORT || 3000;const host = process.env.HOST || '0.0.0.0';const init = async () => {const server = Hapi.server({port,host,routes: {cors: true}});server.route({method: 'GET',path: '/',handler: (request, h) => {return { message: 'Hello from Hapi on Klutch.sh!' };}});server.route({method: 'GET',path: '/health',handler: (request, h) => {return {status: 'healthy',timestamp: new Date().toISOString(),uptime: process.uptime()};}});await server.start();console.log(`Server running at ${server.info.uri}`);};init().catch((err) => {console.error(err);process.exit(1);});
-
Update your `package.json` with startup scripts:
{"name": "my-hapi-app","version": "1.0.0","description": "Hapi app deployed on Klutch.sh","main": "index.js","scripts": {"start": "node index.js","dev": "nodemon index.js"},"dependencies": {"@hapi/hapi": "^21.0.0"},"devDependencies": {"nodemon": "^3.0.1"}}
-
Test your app locally:
Terminal window npm run devVisit http://localhost:3000 to see your app running. Check the health endpoint at http://localhost:3000/health.
Sample Code Examples
Hapi Server with Multiple Routes and Request Validation
Here’s a complete example with multiple routes, input validation, and error handling:
const Hapi = require('@hapi/hapi');const Joi = require('@hapi/joi');
const init = async () => { const server = Hapi.server({ port: process.env.PORT || 3000, host: '0.0.0.0', routes: { cors: { origin: ['*'] }, validate: { failAction: async (request, h, err) => { console.error(err); throw err; } } } });
server.route({ method: 'GET', path: '/', handler: (request, h) => { return { message: 'Welcome to Hapi on Klutch.sh!' }; } });
server.route({ method: 'GET', path: '/api/users/{id}', options: { validate: { params: Joi.object({ id: Joi.number().integer().required() }) } }, handler: (request, h) => { const { id } = request.params; return { id, name: 'John Doe', email: 'john@example.com' }; } });
server.route({ method: 'POST', path: '/api/users', options: { validate: { payload: Joi.object({ name: Joi.string().required(), email: Joi.string().email().required() }) } }, handler: (request, h) => { const { name, email } = request.payload; return { id: Math.floor(Math.random() * 10000), name, email, created: new Date().toISOString() }; } });
await server.start(); console.log(`Server running at ${server.info.uri}`);};
init().catch((err) => { console.error(err); process.exit(1);});Hapi with Plugins
const Hapi = require('@hapi/hapi');
const init = async () => { const server = Hapi.server({ port: process.env.PORT || 3000, host: '0.0.0.0' });
// Register plugins await server.register([ { plugin: require('@hapi/basic'), routes: { prefix: '/api' } } ]);
// Define custom plugin const routes = { plugin: { name: 'routes', version: '1.0.0', register: async function(server, options) { server.route({ method: 'GET', path: '/version', handler: (request, h) => { return { version: '1.0.0' }; } }); } } };
await server.register(routes); await server.start(); console.log(`Server running at ${server.info.uri}`);};
init().catch((err) => { console.error(err); process.exit(1);});Hapi with Database Connection and Caching
const Hapi = require('@hapi/hapi');
const init = async () => { const server = Hapi.server({ port: process.env.PORT || 3000, host: '0.0.0.0', cache: [ { name: 'memory', provider: { constructor: require('@hapi/catbox-memory') } } ] });
// Decorate server with database connection server.decorate('server', 'db', { query: async (sql) => { // Your database query logic return []; } });
server.route({ method: 'GET', path: '/api/data', options: { cache: { expiresIn: 1000 * 60 * 5 } // 5 minutes }, handler: async (request, h) => { const data = await server.db.query('SELECT * FROM data'); return { data }; } });
await server.start(); console.log(`Server running at ${server.info.uri}`);};
init().catch((err) => { console.error(err); process.exit(1);});Project Structure
A typical Hapi project has this structure:
my-hapi-app/├── node_modules/├── routes/ # Route handlers│ ├── index.js│ ├── api.js│ └── users.js├── plugins/ # Custom plugins│ ├── database.js│ └── authentication.js├── utils/ # Utility functions│ ├── validators.js│ └── logger.js├── .env # Environment variables (not committed)├── .gitignore├── index.js # Main application file├── package.json└── package-lock.jsonDeploying Without a Dockerfile
Klutch.sh uses Nixpacks to automatically detect and build your Hapi application. This is the simplest deployment option that requires no additional configuration files.
- Push your Hapi application to a GitHub repository with all your source code, `package.json`, and `package-lock.json` files.
- Log in to your Klutch.sh dashboard.
- Create a new project and give it a name (e.g., "My Hapi API").
-
Create a new app with the following configuration:
- Repository - Select your Hapi GitHub repository and the branch to deploy
- Traffic Type - Select HTTP (for web applications serving HTTP traffic)
- Internal Port - Set to 3000 (the default port for Hapi applications)
- Region - Choose your preferred region for deployment
- Compute - Select the appropriate compute resource size
- Instances - Choose how many instances to run (start with 1 for testing)
- Environment Variables - Add any environment variables your app needs (database URLs, API keys, secrets, etc.)
If you need to customize the start command or build process, you can set Nixpacks environment variables:
START_COMMAND: Override the default start command (e.g.,node index.js)BUILD_COMMAND: Override the default build command
- Click "Create" to deploy. Klutch.sh will automatically detect your Node.js project, install dependencies, and start your Hapi application.
- Once deployed, your app will be available at a URL like `example-app.klutch.sh`. Test it by visiting the URL in your browser and checking the health endpoint at `/health`.
Deploying With a Dockerfile
If you prefer more control over the build and runtime environment, you can use a Dockerfile. Klutch.sh will automatically detect and use any Dockerfile in your repository’s root directory.
-
Create a `Dockerfile` in your project root:
# Multi-stage build for optimized Hapi deploymentFROM node:18-alpine AS builderWORKDIR /app# Copy package filesCOPY package*.json ./# Install dependenciesRUN npm ci# Production stageFROM node:18-alpineWORKDIR /app# Copy built dependencies from builderCOPY --from=builder /app/node_modules ./node_modulesCOPY --from=builder /app/package*.json ./# Copy application codeCOPY . .# Set production environmentENV NODE_ENV=production# Expose port (must match your app's listening port)EXPOSE 3000# Health checkHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \CMD node -e "require('http').get('http://localhost:3000/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"# Start the applicationCMD ["npm", "start"]
-
Create a `.dockerignore` file to exclude unnecessary files from the Docker build:
node_modulesnpm-debug.logdistbuild.git.gitignoreREADME.md.env.env.local.vscode.idea.DS_Store
- Push your code (with Dockerfile and .dockerignore) to GitHub.
-
Follow the same deployment steps as the Nixpacks method:
- Log in to Klutch.sh
- Create a new project
- Create a new app pointing to your GitHub repository
- Set the traffic type to HTTP and internal port to 3000
- Add any required environment variables
- Click “Create”
Klutch.sh will automatically detect your Dockerfile and use it to build and deploy your application.
- Your deployed app will be available at `example-app.klutch.sh` once the build and deployment complete.
Environment Variables & Configuration
Hapi applications can use environment variables for configuration. Set these in the Klutch.sh dashboard during app creation or update them afterward.
Common Environment Variables
# Server configurationPORT=3000HOST=0.0.0.0NODE_ENV=production
# Database connectionDATABASE_URL=postgresql://user:password@db.example.com:5432/mydb
# API Keys and SecretsAPI_KEY=your_api_key_hereSECRET_KEY=your_secret_key_hereJWT_SECRET=your_jwt_secret_here
# CORS configurationCORS_ORIGIN=https://yourdomain.com
# Application settingsLOG_LEVEL=infoDEBUG=falseUsing Environment Variables in Your App
const Hapi = require('@hapi/hapi');
const init = async () => { const port = process.env.PORT || 3000; const host = process.env.HOST || '0.0.0.0'; const nodeEnv = process.env.NODE_ENV || 'development'; const jwtSecret = process.env.JWT_SECRET;
const server = Hapi.server({ port, host, routes: { cors: process.env.CORS_ORIGIN ? { origin: [process.env.CORS_ORIGIN] } : true } });
server.route({ method: 'GET', path: '/config', handler: (request, h) => { return { environment: nodeEnv, hasJwtSecret: !!jwtSecret, port: port }; } });
await server.start(); console.log(`Server running in ${nodeEnv} mode at ${server.info.uri}`);};
init().catch((err) => { console.error(err); process.exit(1);});Customizing Build and Start Commands with Nixpacks
If using Nixpacks deployment without a Dockerfile, you can customize build and start commands by setting environment variables:
BUILD_COMMAND: npm run buildSTART_COMMAND: npm startSet these as environment variables during app creation on Klutch.sh.
Plugins & Performance Optimization
Popular Hapi Plugins
Hapi’s plugin ecosystem provides powerful extensions for common functionality:
const Hapi = require('@hapi/hapi');
const init = async () => { const server = Hapi.server({ port: process.env.PORT || 3000, host: '0.0.0.0' });
// Register plugins await server.register([ { plugin: require('@hapi/cors'), options: { origin: ['*'] } }, { plugin: require('@hapi/jwt'), options: { keys: process.env.JWT_SECRET || 'your-secret-key' } }, { plugin: require('@hapi/basic') }, { plugin: require('@hapi/inert') // Static file serving } ]);
await server.start(); console.log(`Server running at ${server.info.uri}`);};
init().catch((err) => { console.error(err); process.exit(1);});Horizontal Scaling
Klutch.sh makes it easy to scale your Hapi application:
- Multiple Instances - Set the number of instances in the app configuration to distribute traffic across multiple containers
- Load Balancing - Klutch.sh automatically load balances traffic across all instances
- Stateless Design - Ensure your application is stateless so it can run on multiple instances
- Shared Resources - Use external services (Redis, databases) for shared state across instances
Performance Optimization Tips
- Request Validation - Use Hapi’s built-in Joi validation to catch errors early
- Caching - Implement route caching for frequently accessed endpoints
- Connection Pooling - Use connection pools for database connections to manage resources efficiently
- Async/Await - Use async/await properly to handle non-blocking operations
- Compression - Enable payload compression for API responses
- Logging - Configure appropriate log levels to avoid performance impact
Example with optimizations:
const Hapi = require('@hapi/hapi');
const init = async () => { const server = Hapi.server({ port: process.env.PORT || 3000, host: '0.0.0.0', routes: { payload: { compress: true } }, cache: [ { name: 'memory', provider: { constructor: require('@hapi/catbox-memory') } } ] });
// Cached route server.route({ method: 'GET', path: '/api/cached-data', options: { cache: { expiresIn: 1000 * 60 * 5 } // 5 minutes }, handler: async (request, h) => { return { data: 'cached content' }; } });
await server.start(); console.log(`Server running at ${server.info.uri}`);};
init().catch((err) => { console.error(err); process.exit(1);});Troubleshooting
Application Won’t Start
Problem - Deployment completes but the app shows as unhealthy
Solution:
- Check that your app listens on port 3000 (or the port you specified)
- Verify that
process.env.PORTis properly used in your code - Ensure the
startscript inpackage.jsoncorrectly starts your app - Check application logs in the Klutch.sh dashboard for error messages
- Verify that
hostis set to0.0.0.0instead oflocalhost
Build Fails with “Cannot find module”
Problem - Dependencies are not installed during build
Solution:
- Ensure your
package.jsonis in the root directory - Verify all dependencies are listed in
dependencies, not justdevDependencies - Check that dependency names are spelled correctly
- Ensure
package-lock.jsonexists and is up to date
Port Already in Use
Problem - Error about port 3000 already being in use
Solution:
- Make sure only one instance of your app is running
- Check that previous processes are properly terminated
- Verify the PORT environment variable is being used correctly
High Memory Usage
Problem - App uses excessive memory and gets killed
Solution:
- Check for memory leaks in your application code
- Implement proper cleanup for connections and event listeners
- Monitor memory usage during development with the
--inspectflag - Consider increasing the compute tier or number of instances
Slow Response Times
Problem - Requests are responding slowly despite Hapi’s performance
Solution:
- Check for synchronous operations that should be async
- Implement caching for frequently accessed data
- Enable compression for responses
- Profile your application to identify bottlenecks
- Consider upgrading your compute tier
Resources
- Hapi Official Website
- Hapi API Documentation
- Hapi Tutorials
- Hapi Resources
- Nixpacks Documentation
- Klutch.sh Dashboard
- Node.js Documentation
Summary
Deploying a Hapi application on Klutch.sh is straightforward whether you choose Nixpacks or Docker. Both methods provide reliable, scalable hosting for your robust APIs and web applications. Start with Nixpacks for simplicity and quick deployments, or use Docker for more control over your build environment. With Klutch.sh’s easy scaling, automatic load balancing, and environment variable management, you can deploy your Hapi app from development to production and scale it to handle enterprise-grade workloads with ease.