By the end of this lesson, you will be able to:
💡 Real-World Connection: Most applications you use daily have APIs powering them. Instagram's app talks to Instagram's API, Netflix's interface connects to Netflix's API. Now you'll build your own!
So far, you've been consuming other people's APIs. Now it's time to flip the script and become an API provider! You'll create a simple API that your application can use to save favorite items and user notes.
Express.js is a minimal web framework for Node.js that makes creating APIs straightforward.
# Create a new directory for your API
mkdir my-api
cd my-api
# Initialize npm project
npm init -y
# Install Express and other dependencies
npm install express cors
npm install --save-dev nodemon
// server.js
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = 3001; // Different from your frontend port
// Middleware
app.use(cors()); // Allow requests from your frontend
app.use(express.json()); // Parse JSON requests
// In-memory storage (we'll upgrade this later)
let favorites = [];
let notes = [];
let nextId = 1;
// Test endpoint
app.get('/', (req, res) => {
res.json({
message: 'API is running!',
timestamp: new Date().toISOString()
});
});
// Start the server
app.listen(PORT, () => {
console.log(`🌐 API server running on http://localhost:${PORT}`);
});
Add this script to your package.json
:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
Start your server:
npm run dev
Visit http://localhost:3001
in your browser - you should see your API response!
Let's create endpoints to retrieve data from your API.
// GET all favorite items
app.get('/api/favorites', (req, res) => {
res.json({
success: true,
data: favorites,
count: favorites.length
});
});
// GET a specific favorite by ID
app.get('/api/favorites/:id', (req, res) => {
const id = parseInt(req.params.id);
const favorite = favorites.find(fav => fav.id === id);
if (!favorite) {
return res.status(404).json({
success: false,
message: 'Favorite item not found'
});
}
res.json({
success: true,
data: favorite
});
});
// GET all notes
app.get('/api/notes', (req, res) => {
// Optional: filter by category
const category = req.query.category;
let filteredNotes = notes;
if (category) {
filteredNotes = notes.filter(note =>
note.category.toLowerCase().includes(category.toLowerCase())
);
}
res.json({
success: true,
data: filteredNotes,
count: filteredNotes.length,
filters: { category }
});
});
// GET a specific note by ID
app.get('/api/notes/:id', (req, res) => {
const id = parseInt(req.params.id);
const note = notes.find(n => n.id === id);
if (!note) {
return res.status(404).json({
success: false,
message: 'Note not found'
});
}
res.json({
success: true,
data: note
});
});
Now let's add endpoints to create new data.
// POST - Add a new favorite item
app.post('/api/favorites', (req, res) => {
const { name, category, description, metadata } = req.body;
// Basic validation
if (!name || !category) {
return res.status(400).json({
success: false,
message: 'Name and category are required'
});
}
// Check if item already exists
const exists = favorites.find(fav =>
fav.name.toLowerCase() === name.toLowerCase() &&
fav.category.toLowerCase() === category.toLowerCase()
);
if (exists) {
return res.status(409).json({
success: false,
message: 'Item already in favorites'
});
}
// Create new favorite
const newFavorite = {
id: nextId++,
name,
category,
description: description || null,
metadata: metadata || null,
addedAt: new Date().toISOString()
};
favorites.push(newFavorite);
res.status(201).json({
success: true,
message: 'Favorite item added',
data: newFavorite
});
});
// POST - Add a new note
app.post('/api/notes', (req, res) => {
const { category, note, title, tags } = req.body;
// Validation
if (!category || !note) {
return res.status(400).json({
success: false,
message: 'Category and note content are required'
});
}
// Create new note
const newNote = {
id: nextId++,
category,
note,
title: title || null,
tags: tags || null,
createdAt: new Date().toISOString()
};
notes.push(newNote);
res.status(201).json({
success: true,
message: 'Note added',
data: newNote
});
});
Complete your CRUD operations with PUT and DELETE endpoints.
// PUT - Update a favorite item
app.put('/api/favorites/:id', (req, res) => {
const id = parseInt(req.params.id);
const favoriteIndex = favorites.findIndex(fav => fav.id === id);
if (favoriteIndex === -1) {
return res.status(404).json({
success: false,
message: 'Favorite not found'
});
}
const { name, category, description, metadata } = req.body;
// Update the favorite
favorites[favoriteIndex] = {
...favorites[favoriteIndex],
name: name || favorites[favoriteIndex].name,
category: category || favorites[favoriteIndex].category,
description: description !== undefined ? description : favorites[favoriteIndex].description,
metadata: metadata !== undefined ? metadata : favorites[favoriteIndex].metadata,
updatedAt: new Date().toISOString()
};
res.json({
success: true,
message: 'Favorite updated',
data: favorites[favoriteIndex]
});
});
// DELETE - Remove a favorite item
app.delete('/api/favorites/:id', (req, res) => {
const id = parseInt(req.params.id);
const favoriteIndex = favorites.findIndex(fav => fav.id === id);
if (favoriteIndex === -1) {
return res.status(404).json({
success: false,
message: 'Favorite not found'
});
}
const deletedFavorite = favorites.splice(favoriteIndex, 1)[0];
res.json({
success: true,
message: 'Favorite item removed',
data: deletedFavorite
});
});
// DELETE - Remove a note
app.delete('/api/notes/:id', (req, res) => {
const id = parseInt(req.params.id);
const noteIndex = notes.findIndex(note => note.id === id);
if (noteIndex === -1) {
return res.status(404).json({
success: false,
message: 'Note not found'
});
}
const deletedNote = notes.splice(noteIndex, 1)[0];
res.json({
success: true,
message: 'Note deleted',
data: deletedNote
});
});
Now integrate your API with your application frontend.
Add these functions to your weather dashboard:
// api.js - Add to your application
const API_BASE_URL = 'http://localhost:3001/api';
// Favorite item functions
async function addFavoriteItem(name, category, description, metadata) {
try {
const response = await fetch(`${API_BASE_URL}/favorites`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, category, description, metadata })
});
const result = await response.json();
if (result.success) {
console.log('Added to favorites:', result.data);
displayFavorites(); // Refresh the favorites list
} else {
alert(result.message);
}
} catch (error) {
console.error('Error adding favorite:', error);
}
}
async function getFavorites() {
try {
const response = await fetch(`${API_BASE_URL}/favorites`);
const result = await response.json();
if (result.success) {
return result.data;
}
} catch (error) {
console.error('Error fetching favorites:', error);
return [];
}
}
// Note functions
async function addNote(category, note, title, tags) {
try {
const response = await fetch(`${API_BASE_URL}/notes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ category, note, title, tags })
});
const result = await response.json();
if (result.success) {
console.log('Note added:', result.data);
displayNotes(); // Refresh the notes display
}
} catch (error) {
console.error('Error adding note:', error);
}
}
Add these to your application HTML:
<!-- Favorites Section -->
<div class="favorites-section">
<h3>Favorite Items</h3>
<button onclick="addCurrentItemToFavorites()">⭐ Add Current to Favorites</button>
<div id="favorites-list"></div>
</div>
<!-- Notes Section -->
<div class="notes-section">
<h3>Notes</h3>
<textarea id="note-input" placeholder="Add a note..."></textarea>
<button onclick="saveNote()">💾 Save Note</button>
<div id="notes-list"></div>
</div>
Test GET endpoints directly in your browser:
http://localhost:3001/api/favorites
http://localhost:3001/api/notes
Test POST requests using the browser console:
// Test adding a favorite
fetch('http://localhost:3001/api/favorites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Sample Item',
category: 'Example',
description: 'A test item',
metadata: { type: 'demo' }
})
})
.then(res => res.json())
.then(data => console.log(data));
<!-- test.html -->
<!DOCTYPE html>
<html>
<head>
<title>API Tester</title>
</head>
<body>
<h1>API Tester</h1>
<div>
<h3>Add Favorite</h3>
<input id="name" placeholder="Name">
<input id="category" placeholder="Category">
<button onclick="testAddFavorite()">Add Favorite</button>
</div>
<div>
<h3>Get Favorites</h3>
<button onclick="testGetFavorites()">Get All Favorites</button>
<div id="favorites-output"></div>
</div>
<script>
const API_URL = 'http://localhost:3001/api';
async function testAddFavorite() {
const name = document.getElementById('name').value;
const category = document.getElementById('category').value;
const response = await fetch(`${API_URL}/favorites`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, category })
});
const result = await response.json();
alert(JSON.stringify(result, null, 2));
}
async function testGetFavorites() {
const response = await fetch(`${API_URL}/favorites`);
const result = await response.json();
document.getElementById('favorites-output').innerHTML =
`<pre>${JSON.stringify(result, null, 2)}</pre>`;
}
</script>
</body>
</html>
CORS (Cross-Origin Resource Sharing) allows your frontend and backend to communicate when they're on different ports.
// Simple CORS (already included in our setup)
app.use(cors());
// More specific CORS configuration
app.use(cors({
origin: ['http://localhost:3000', 'http://127.0.0.1:3000'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
Without CORS, your browser would block requests from http://localhost:3000
(your application) to http://localhost:3001
(your API) because they're different origins.
Here's your complete server.js
file:
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = 3001;
// Middleware
app.use(cors());
app.use(express.json());
// In-memory storage
let favorites = [];
let notes = [];
let nextId = 1;
// Root endpoint
app.get('/', (req, res) => {
res.json({
message: 'API is running!',
timestamp: new Date().toISOString(),
endpoints: {
favorites: '/api/favorites',
notes: '/api/notes'
}
});
});
// Favorites endpoints
app.get('/api/favorites', (req, res) => {
res.json({ success: true, data: favorites, count: favorites.length });
});
app.post('/api/favorites', (req, res) => {
const { name, category, description, metadata } = req.body;
if (!name || !category) {
return res.status(400).json({
success: false,
message: 'Name and category are required'
});
}
const newFavorite = {
id: nextId++,
name,
category,
description: description || null,
metadata: metadata || null,
addedAt: new Date().toISOString()
};
favorites.push(newFavorite);
res.status(201).json({ success: true, data: newFavorite });
});
app.delete('/api/favorites/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = favorites.findIndex(fav => fav.id === id);
if (index === -1) {
return res.status(404).json({ success: false, message: 'Favorite not found' });
}
const deleted = favorites.splice(index, 1)[0];
res.json({ success: true, message: 'Favorite removed', data: deleted });
});
// Notes endpoints
app.get('/api/notes', (req, res) => {
const category = req.query.category;
let filteredNotes = category
? notes.filter(note => note.category.toLowerCase().includes(category.toLowerCase()))
: notes;
res.json({ success: true, data: filteredNotes, count: filteredNotes.length });
});
app.post('/api/notes', (req, res) => {
const { category, note, title, tags } = req.body;
if (!category || !note) {
return res.status(400).json({
success: false,
message: 'Category and note content are required'
});
}
const newNote = {
id: nextId++,
category,
note,
title: title || null,
tags: tags || null,
createdAt: new Date().toISOString()
};
notes.push(newNote);
res.status(201).json({ success: true, data: newNote });
});
// Start server
app.listen(PORT, () => {
console.log(`🌐 API server running on http://localhost:${PORT}`);
console.log(`📋 Test endpoints at http://localhost:${PORT}/api/favorites`);
});
Congratulations! You've built your first API with:
Your API now provides the backend functionality for your application to save favorite items and user notes!
GET /api/notes/search?query=keyword
to search notes by contentIn the next lesson, we'll explore advanced API concepts including authentication, data persistence with databases, and deployment strategies. Your simple API is the foundation for building robust, production-ready applications!