Hapi.js is a feature-rich Node.js framework specifically designed for building scalable web applications and APIs.
Known for its powerful plugin system, built-in validation, caching, authentication, and comprehensive security features, Hapi.js provides developers with a solid foundation for enterprise-grade applications.
The framework emphasizes configuration over code, offering extensive built-in functionality that reduces the need for external dependencies while maintaining excellent performance and reliability.
In this comprehensive tutorial, you'll construct a complete blog API using Hapi.js alongside Mongoose for MongoDB integration. We'll explore fundamental Hapi.js concepts while building full CRUD functionality for managing blog posts, including creation, retrieval, modification, and deletion operations.
Prerequisites
Before diving into development, ensure you have the following installed:
- Node.js installed on your machine (preferably Node.js 20 or higher)
- MongoDB running locally or access to a MongoDB cloud instance
- Basic familiarity with JavaScript, Node.js, and REST API concepts
Step 1 — Setting up the Hapi.js project
In this section, you'll establish the project directory structure and install the required dependencies for your Hapi.js blog API.
Begin by creating a new directory for your project and navigating into it:
Initialize a new Node.js project:
Install Hapi.js and the essential dependencies for your project:
Here's what each package provides:
@hapi/hapi: The core Hapi.js framework for building web servers and APIsmongoose: An elegant MongoDB object modeling library for Node.jsjoi: A powerful data validation library that integrates seamlessly with Hapi.js@hapi/boom: HTTP-friendly error objects for consistent error handling
Update your package.json to enable ESM modules and add development scripts:
The "type": "module" field enables ESM module support, while the --watch flag provides built-in file watching without needing external tools.
Create the initial server file with a modern ESM-based Hapi.js setup:
This configuration sets up a Hapi.js server running on port 3000 with CORS enabled for cross-origin requests. The basic route responds to GET requests at the root path with a welcome message.
Start your development server using Node.js built-in watch mode:
You should see output confirming the server is running:
Test the server using the browser of your choice by visiting http://localhost:3000:
If you're using Postman for testing, create a new request with these settings:
The response should be:
Node.js will automatically restart the server whenever you modify your files, providing a smooth development experience without additional dependencies.
Your Hapi.js foundation is now established and operational. In the next step, you'll integrate MongoDB using Mongoose for data persistence.
Step 2 — Configuring MongoDB with Mongoose
With your Hapi.js server running, the next phase involves setting up MongoDB connectivity using Mongoose. This will provide the data layer for storing and managing your blog posts.
First, create a structured directory layout for your application components:
Create a database configuration file:
The configuration establishes a connection to MongoDB using either an environment variable or a default local connection string. It includes proper error handling and cleanup functions.
Update your main server file to integrate the database connection:
The updated server integrates database connectivity and includes graceful shutdown handling to close database connections when the server stops properly.
Restart your server to see the MongoDB connection in action:
You should see output indicating successful database connection:
If MongoDB isn't running locally, you'll see a connection error. Make sure MongoDB is installed and running, or update the connection string to point to your MongoDB instance.
With the database layer configured, you're ready to create data models for your blog posts in the next step.
Step 3 — Creating the blog post model and validation schemas
Now that MongoDB is integrated, you'll define the data structure for blog posts and establish validation rules using Joi schemas. This ensures data consistency and provides automatic request validation.
Create the Mongoose model for blog posts:
This model defines the structure for blog posts with proper validation constraints. The timestamps: true option automatically manages creation and update timestamps.
Next, create Joi validation schemas for request validation:
These schemas define validation rules for creating posts, updating posts, and validating MongoDB ObjectIds in URL parameters. The custom error messages provide clear feedback when validation fails.
Create a simple controller to handle business logic:
The controller separates business logic from route handling and provides consistent error handling using Boom for HTTP-friendly error responses.
With your data models and validation schemas established, you can proceed to implement the API endpoints in the next step.
Step 4 — Creating blog posts
Now you'll implement the first API endpoint for creating new blog posts. This will demonstrate how Hapi.js handles request validation, route configuration, and response formatting.
Create the routes file for post-related endpoints:
This route configuration includes automatic payload validation using the Joi schema, API documentation tags, and proper HTTP status code handling for successful creation (201).
Update your main server file to register the routes:
The server now registers all post routes, making the POST endpoint available for creating blog posts.
Test the endpoint by creating a new blog post using curl:
For Postman testing, configure your request as shown:
You should receive a response similar to:
Test the validation by sending invalid data:
You should receive a 400 Bad Request response with validation details:
Hapi.js automatically validates incoming requests against your Joi schemas and returns descriptive error messages when validation fails, providing excellent developer experience out of the box.
Step 5 — Retrieving blog posts
With post creation implemented, you'll now add endpoints for retrieving blog posts. This includes listing all posts with optional filtering and fetching individual posts by their ID.
The controller methods for retrieval are already in place from Step 3. Now let's add the routes to make them accessible via HTTP endpoints.
Add the retrieval routes to your routes file:
These routes provide comprehensive retrieval functionality with query parameter validation for filtering and path parameter validation for individual post lookups.
Test retrieving all posts using curl:
For Postman, create a GET request:
You should receive an array of all blog posts:
Test filtering by published status:
Test retrieving a specific post by ID (replace with an actual ID):
For Postman, use the individual post endpoint:
Test error handling with an invalid ID:
You should receive a 400 Bad Request response:
Your API now supports comprehensive post retrieval with filtering capabilities and robust error handling.
Step 6 — Updating blog posts
Next, you'll implement the functionality to update existing blog posts. This endpoint will support partial updates, allowing clients to modify only specific fields without affecting others.
Add the update method to your controller:
The update method uses findByIdAndUpdate with validation enabled and returns the updated document. It handles various error conditions including validation failures and invalid IDs.
Add the PUT route to your routes file:
The PUT route validates both the post ID parameter and the update payload, making sure only valid data can be processed.
Test updating a blog post using curl (replace with an actual post ID):
You should receive the updated post:
Notice that the updatedAt timestamp reflects the modification time while createdAt remains unchanged.
Test partial updates by modifying only the published status:
The response should show only the published field changed while other fields remain intact:
That takes care of updating posts.
Step 7 — Deleting blog posts
The final step in completing your CRUD functionality is implementing the delete endpoint. This will allow users to remove blog posts from the database when they're no longer needed.
Add the delete method to your controller:
The delete method removes the post from the database and returns a confirmation message along with the deleted post data for reference.
Add the DELETE route to your routes file:
The DELETE route validates the post ID parameter and returns a 200 status code upon successful deletion.
Test the delete functionality by first creating a post specifically for deletion:
Note the ID from the response, then delete the post:
You should receive a confirmation response:
Verify the deletion by attempting to retrieve the same post:
You should receive a 404 Not Found response:
Your Hapi.js blog API now supports complete CRUD functionality with comprehensive error handling and validation.
Final thoughts
You've successfully built a fully functional RESTful blog API using Hapi.js and MongoDB. Your API now supports all CRUD operations: creating, reading, updating, and deleting blog posts with proper validation, error handling, and response formatting.
To further enhance your blog API, consider implementing user authentication with JWT tokens, adding pagination for large result sets, implementing full-text search capabilities, adding rate limiting for API protection, integrating automated testing with Jest or Lab, and implementing API documentation with Swagger integration.
For continued learning and exploring advanced Hapi.js capabilities, visit the official Hapi.js documentation and explore the extensive ecosystem of plugins available for extending your applications.