Building a Trends API in Node.js
Article Navigation By Section
Introduction
This article explains how to build a modular Trends API in Node.js using Express. We’ll cover routes, controllers, models, and database connections, plus a glossary of technical terms.
Routes: src/api/routes/trends.js
Routes define API endpoints and map them to controller functions.
const express = require('express');
const router = express.Router();
const { getTrends, createTrend, deleteTrend } = require('../controllers/trendsController');
router.get('/', getTrends);
router.post('/', createTrend);
router.delete('/:id', deleteTrend);
module.exports = router;
This code sets up the routes for your Trends API using Express. First, it imports the Express library and
creates a router instance, which acts as a mini-application just for the /trends part of your API.
Then it imports three controller functions: getTrends, createTrend, and deleteTrend from the
trends controller file.
The three router lines define what should happen when someone calls this API. A GET request to / (for example, /api/trends) will call getTrends to fetch data. A POST request to the same path will call createTrend to create a new trend entry. A DELETE request with an :id parameter (for example, /api/trends/5) will call deleteTrend to remove a specific trend.
Finally, module.exports = router; makes this router available to the rest of your app. In your main server file, you would typically mount this router under a path like /api/trends, so all these routes become part of your public API.
Imagine your API is a building and each feature (like trends) is its own department. The router is the receptionist desk for the Trends department. When visitors arrive and say, “I want to see all trends” (GET), “I want to add a new trend” (POST), or “I want to delete trend number 5” (DELETE), the receptionist knows exactly which internal staff member (controller function) to send them to.
- Technical note: Express routers help you keep your routes modular, so you don’t crowd everything into a single giant
app.jsfile. - Technical note: Using
router.get,router.post, androuter.deletefollows RESTful conventions, which makes your API predictable for other developers. - Technical note: The
/:idsegment is called a URL parameter, and it will be available asreq.params.idin your controller.
Controllers: src/api/controllers/trendsController.js
Controllers handle business logic and interact with models.
const getTrends = async (req, res) => {
try {
const trends = [{ id: 1, name: "Organic Farming", popularity: 95 }];
res.status(200).json(trends);
} catch (error) {
res.status(500).json({ message: "Error fetching trends" });
}
};
This controller function is responsible for returning a list of trends when the client makes a GET
request. It’s marked as async, which means it can handle asynchronous operations, such as fetching data
from a database. Inside the try block, we currently return a hard-coded array with one trend object, just
as a simple example or placeholder.
If everything works, the controller responds with a 200 HTTP status code (meaning “OK”) and sends the trend data
back as JSON using res.json(). If something goes wrong inside the try block, the catch section will run
instead, logging the error if needed and returning a 500 status code, which means “Internal Server Error,” along with a JSON error message.
In a real application, this function would likely call a model method (for example, Trend.find()) to fetch data
from the database. The structure of the function — try/catch, status codes, and JSON responses — is what makes it a clean,
reusable controller pattern.
Think of this controller as a librarian in charge of a “Trends” section. When someone asks, “Can you show me all the current trends?” the librarian goes to the shelf, gathers the relevant books (data), and hands them back neatly arranged (JSON response). If the librarian finds the shelf room locked or discovers a problem with the catalog (an error), they return and honestly say, “There was an internal problem fetching your request,” instead of pretending everything is fine.
- Technical note: Marking the function
asyncmakes it easy to plug in real database calls later usingawait. - Technical note: Using
res.status(200).json(...)is a standard pattern that clearly communicates success and returns machine-readable data. - Technical note: Returning
500in thecatchblock is a good default for unexpected errors, but you can enhance this by logging the error or using centralized error-handling middleware.
Models: src/api/models/Trend.js
Models define the database schema. Example with Mongoose:
const mongoose = require('mongoose');
const trendSchema = new mongoose.Schema({
name: { type: String, required: true },
popularity: { type: Number, default: 0 }
});
module.exports = mongoose.model('Trend', trendSchema);
This code sets up a Mongoose model for a “Trend” document in MongoDB. First, it imports Mongoose, which is the
library that lets you define schemas and models in a structured way. Then it defines a schema called
trendSchema that describes what each Trend document should look like in the database.
The schema says that each trend has a name, which is a string and is required (you cannot save a trend without it),
and a popularity field, which is a number with a default value of 0 if not provided. These rules help
enforce consistent structure and types across your data.
Finally, mongoose.model('Trend', trendSchema) compiles the schema into a model called Trend. This model is
what you will use in your controllers or services to create, read, update, and delete trend data in the database
— for example, Trend.find(), Trend.create(), and so on.
Think of the schema as a blueprint for building houses in a specific estate. Every house must have certain rooms (fields) such as a bedroom (name) and a kitchen (popularity), and they must follow certain rules (required, default values). The model is like the construction company that uses this blueprint: when you place an order for a new house, they know exactly how to build it and where to store it in the estate.
- Technical note: Schemas allow you to define data types, validation rules, and default values at the database level, reducing bugs.
- Technical note: By exporting
mongoose.model('Trend', trendSchema), you create a reusable model that can be imported anywhere else in your codebase. - Technical note: Mongoose models automatically map to a MongoDB collection (by default,
trendsfor theTrendmodel, using a pluralized, lowercased name).
Database Connection: src/api/config/database.js
Database connection ensures persistence. Example with MongoDB:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log("MongoDB Connected");
} catch (error) {
console.error("Error connecting to DB", error);
process.exit(1);
}
};
module.exports = connectDB;
This code is responsible for establishing a connection between your Node.js application and your MongoDB database.
It imports Mongoose, then defines an asynchronous function called connectDB. Inside the try block,
it calls mongoose.connect() using a connection string stored in process.env.MONGO_URI, which is typically defined
in your .env file for security.
If the connection succeeds, it logs a simple confirmation message, “MongoDB Connected,” to the console. If any error occurs
while trying to connect, the catch block logs an error message and the error itself, then calls process.exit(1) to stop the Node.js process. This is a way of failing fast — you don’t want the server to keep running if it cannot connect to the database it depends on.
By exporting connectDB, you can call this function from your main server file (for example, server.js or app.js)
during startup. This keeps your database connection logic isolated and reusable, instead of mixing it with route or business logic.
Imagine your app is a restaurant and the database is the central pantry where all ingredients are stored. This connection function is like the pipeline that connects your kitchen directly to the pantry. If that pipeline is broken, there’s no point in opening the restaurant because the chefs can’t get any ingredients. So if the pipeline fails, the restaurant closes immediately instead of pretending it can still serve customers.
- Technical note: Storing the connection string in
process.env.MONGO_URIkeeps sensitive credentials out of your source code. - Technical note: Using
async/awaitwithmongoose.connect()makes the startup flow easier to reason about than nested callbacks. - Technical note:
process.exit(1)is a conventional way to signal an abnormal termination when the app cannot start correctly.
Glossary of Terms
| Term | Definition |
|---|---|
| Express Router | Modular routing system in Express.js used to organize endpoints. |
| Controller | Functions that implement business logic and respond to API requests. |
| Model | Represents database structure and provides an interface for CRUD operations. |
| Schema | Blueprint defining fields, types, and validation rules for database documents. |
| Mongoose | ODM library for MongoDB that provides schemas and models. |
| Sequelize | ORM library for SQL databases such as MySQL and PostgreSQL. |
| Environment Variables |
Secure configuration values stored in .env files.
|
| CRUD | Create, Read, Update, Delete — the four basic data operations. |
| HTTP Methods | GET, POST, PUT/PATCH, DELETE — actions used in REST APIs. |
| Middleware | Functions that run before route handlers to process requests. |
| REST API | Architectural style using HTTP methods and resource-based URLs. |
SEO Checklist
- Keyword-rich title and headings
- Meta description within 160 characters
- Canonical URL set
- Open Graph and Twitter tags aligned
- Schema markup included
- Internal navigation with href anchors
- Accessible link text and semantic headings
No comments:
Post a Comment