Contact API Routes and Controller — Full Breakdown
A complete breakdown of how src/api/routes/contacts.js and
src/api/controllers/contactsController.js work together in an Express.js Contact API, including
technical explanations and a beginner-friendly analogy.
Navigate the article by sections:
Routes File: src/api/routes/contacts.js
The file src/api/routes/contacts.js is typically used in a Node.js Express application to define
the API routes (endpoints) specifically for managing contact resources. It uses the Express Router to group
routes related to a single entity, which makes your application modular and cleaner.
Here is a common structure for what this file might contain, following RESTful conventions for a Contacts API:
const express = require('express');
const router = express.Router();
const contactsController = require('../controllers/contactsController'); // Assuming you have a controller for logic
// Base path for these routes is usually set in a higher-level file (e.g., app.js)
// For example, if mounted at '/api/contacts', the path '/' here corresponds to '/api/contacts'
// --- GET All Contacts and POST New Contact (Collection Route) ---
// GET /
// Purpose: Retrieve a list of all contacts
router.get('/', contactsController.getAllContacts);
// POST /
// Purpose: Create a new contact
router.post('/', contactsController.createContact);
// --- Routes for a Specific Contact (Resource Route) ---
// GET /:id
// Purpose: Retrieve a single contact by its ID
router.get('/:id', contactsController.getContactById);
// PUT /:id
// Purpose: Update an existing contact by its ID (usually replacing the entire resource)
router.put('/:id', contactsController.updateContact);
// PATCH /:id
// Purpose: Partially update an existing contact by its ID
// router.patch('/:id', contactsController.partialUpdateContact); // Less common, but good practice
// DELETE /:id
// Purpose: Delete a specific contact by its ID
router.delete('/:id', contactsController.deleteContact);
module.exports = router;
This routes file maps HTTP methods and URL paths to controller functions, following clean RESTful conventions for
the /contacts resource.
contactsController.
Key Concepts Illustrated
This Contact API routing setup highlights several important Express and REST design concepts.
-
express.Router():
Creates a modular, mountable route handler. This keeps the routes for one resource
(
contacts) separate from others (likeusersorproducts). -
RESTful Routing:
The routes follow the standard conventions of using HTTP methods (
GET,POST,PUT,DELETE) with specific paths to perform CRUD (Create, Read, Update, Delete) operations on thecontactsresource. -
Route Parameters (/:id):
The
:idis a route parameter that captures the ID from the URL (e.g., in/contacts/123), making it accessible in the controller viareq.params.id. - Separation of Concerns: The file focuses only on routing. The actual business logic (database queries, validation, etc.) lives in the controller and model layers.
Long Technical Explanation
The file src/api/routes/contacts.js is responsible for defining all the HTTP endpoints related to the
Contacts resource in your Express application. It maps URLs and HTTP methods to specific controller functions.
When a client sends a request—whether from a browser, mobile app, or Postman—Express needs to know which function should handle that request. This file acts as the routing layer that connects:
- The URL path
- The HTTP method (GET, POST, PUT, DELETE, PATCH)
- The controller function that contains the logic
For example:GET /api/contacts→ fetch all contacts
POST /api/contacts→ create a new contact
GET /api/contacts/:id→ retrieve one contact by ID
The file uses express.Router() to group all contact-related routes together. This keeps your main
app.js clean and allows you to mount the router under a base path like:
app.use('/api/contacts', contactsRoutes);
This means any request starting with /api/contacts is automatically forwarded to this route file,
which then picks the correct controller function to execute.
Controller File: src/api/controllers/contactsController.js
The Controller file holds the crucial business logic that executes when a route is hit. It keeps the routes clean
and the application scalable. It typically uses asynchronous patterns (like async/await) and
interacts with a Contact model that handles database access.
// A hypothetical model for interacting with the database
// In a real application, this would contain Sequelize, Mongoose, or raw SQL queries.
const Contact = require('../models/Contact');
// --- 1. GET All Contacts ---
const getAllContacts = async (req, res) => {
try {
// Logic: Fetch all contacts from the database
const contacts = await Contact.findAll();
// Response: Send a successful response (HTTP 200 OK) with the data
res.status(200).json({
success: true,
count: contacts.length,
data: contacts
});
} catch (error) {
// Error Handling: Log the error and send a server error response
console.error(error);
res.status(500).json({
success: false,
message: 'Server Error: Could not retrieve contacts.'
});
}
};
// --- 2. POST Create New Contact ---
const createContact = async (req, res) => {
try {
const { name, email, phone } = req.body;
// Logic: Create a new contact in the database
const newContact = await Contact.create({ name, email, phone });
// Response: Send a successful creation response (HTTP 201 Created)
res.status(201).json({
success: true,
data: newContact
});
} catch (error) {
console.error(error);
// You'd typically add validation error checks here (e.g., 400 Bad Request)
res.status(500).json({
success: false,
message: 'Server Error: Could not create contact.'
});
}
};
// --- 3. GET Contact by ID ---
const getContactById = async (req, res) => {
try {
// Access the ID from the route parameter defined in routes/contacts.js
const contact = await Contact.findById(req.params.id);
if (!contact) {
// If no contact is found, send a 404 Not Found response
return res.status(404).json({
success: false,
message: `Contact with ID ${req.params.id} not found.`
});
}
// Response: Send the contact data
res.status(200).json({
success: true,
data: contact
});
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: 'Server Error: Could not retrieve contact.'
});
}
};
// --- 4. PUT Update Contact ---
const updateContact = async (req, res) => {
try {
const updatedCount = await Contact.update(req.params.id, req.body);
// Check if any rows were affected/updated
if (updatedCount === 0) {
return res.status(404).json({
success: false,
message: `Contact with ID ${req.params.id} not found for update.`
});
}
// Fetch the updated contact to return it (optional but helpful)
const updatedContact = await Contact.findById(req.params.id);
// Response: Send the updated contact data
res.status(200).json({
success: true,
data: updatedContact,
message: 'Contact updated successfully.'
});
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: 'Server Error: Could not update contact.'
});
}
};
// --- 5. DELETE Contact ---
const deleteContact = async (req, res) => {
try {
const deletedCount = await Contact.delete(req.params.id);
if (deletedCount === 0) {
return res.status(404).json({
success: false,
message: `Contact with ID ${req.params.id} not found for deletion.`
});
}
// Response: Send a successful no-content response (HTTP 204 No Content)
res.status(204).json({
success: true,
data: {}
});
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: 'Server Error: Could not delete contact.'
});
}
};
module.exports = {
getAllContacts,
createContact,
getContactById,
updateContact,
deleteContact,
};
The controller is the “brain” for each route: it reads inputs, calls the model, handles errors, and shapes the HTTP response.
Contact.findAll()) and react to the result.
Controller Responsibilities
The controller's job is to sit between the routes and the models, ensuring that incoming requests are understood correctly and outgoing responses are properly structured.
-
Read Request Data:
Extract information from the request, such as route parameters (
req.params.id), query strings (req.query), and the body (req.body). -
Interact with the Model:
Call the appropriate methods on the
Contactmodel to perform database operations (likeContact.findAll()orContact.create()). - Handle Errors and Success: Manage the outcome of the model interaction, including checking for non-existent resources (404) or database errors (500).
- Send Responses: Format and send the appropriate HTTP status code and JSON response back to the client.
Beginner-Friendly Analogy
Imagine your API is a large company office. Inside this office:
-
The Reception Desk =
routes/contacts.js - The Employees = controllers
- The Filing Cabinet = models/database
When a visitor walks into the building, the receptionist listens to what they want and directs them to the correct department. The receptionist does not do the work—they only route the request.
“I want to see all contacts.” → Reception sends them to the Get All Contacts department.
“I want to add a new contact.” → Reception sends them to the Create Contact department.
The employees (controllers) then perform the actual work, such as fetching records or updating information. When they need to access or modify data, they go to the filing cabinet (models/database).
This analogy helps beginners understand why the route file exists and why it should remain clean and free of business logic, while controllers and models handle the heavy lifting behind the scenes.
No comments:
Post a Comment