Server Side JavaScript Building Guide
If you are using a database and serving user content over that database on a website, then you are using a server side language. Unfortunately for you, it's JavaScript. If you are new, I would figure out what user content you want to server on that website and how you want your users to interact with that content on that website.
To get started with Server Side JavaScript, you'll need to install dependencies and then see how a template project looks like. Server side Javascript is one of those languages were you need to see template projects before you create your own. Throwing someone in the deep end and expecting them to build everything at once without seeing the whole picture is really annoying and stupid. Server Side Javascript is a language where you need to model and draw out everything before you start building. Build one thing at a time and TEST everything once you've built it.
Install Node.js
The First step is to install Node.js, it's highly recommended that you use NVM (Node Version Manager). The NVM is available both on linux and mac. For current details refer to the node.js documentation.
Open terminal and install NVM:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
Download and install Node.js using NVM:
nvm install 22
Verify the Node.js version:
node -v
Verify the npm version:
npm -v
Now it's time to create a new project. Create a folder and open it with vscodium. Then initialize node.js for your project within terminal within vscodium:
npm init
Your directory should look like this:
Install a Back-End Framework (Express.js)
npm install express
After that we can install express.
Talk about public folder.
This app.js is really important and will set the stage for everything.
app.js
npm run start
localhost:3000
Different JavaScript Frameworks do different things. Make sure you do your homework and use the framework with it's intended use. Each JavaScript framework has different technical parts to each - get it's overview before you use it.
Install a Database (Mongodb)
There are many databases you could use with express.js including: MySQL, PostgreSQL, SQLite CouchDB, and Redis. Pretty much all the big ones. In this tutorial we will use Mongodb which is a NoSQL, but feel free to use whatever you want, just read their documentation and adjust your project accordingly.
Mongodb Community Edition. You can see more installation details here.
MacOS
brew tap mongodb/brew
brew update
Replace 8.0 with the current version.
brew install mongodb-community@8.0
Linux (Debian)
sudo apt-get install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg \
--dearmor
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ]
http://repo.mongodb.org/apt/debian bookworm/mongodb-org/8.0 main" |
sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
Mongosh. Mongosh is open source.
Mac
brew install mongosh
Linux (Debian)
wget -qO- https://www.mongodb.org/static/pgp/server-8.0.asc | sudo tee /etc/apt/trusted.gpg.d/server-8.0.asc
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
sudo apt-get update
sudo apt-get install -y mongodb-mongosh
mongosh --version
MongoDB extension
npm install mongodb
database.js
Install and Setup GoodBooks
Show example outline diagram
GoodBooks - download file hereThe following code shows how to utilize Server Side JavaScript to fetch data on a website. Server Side JavaScript is used when you want to authenticate/add users through a database, fetch a lot of content to render that content dynamically, and add content to a database based on the user's decisions.
app.js
Import the routes file (users.js) and (books.js) to be used with the usersRouter and booksRouter:
// Import Routers
const usersRouter = require('./routes/users');
const booksRouter = require('./routes/books');
Creates an Express application instance:
const app = express();
Mount usersRouter to the '/api' path and mount booksRouter to the '/books' path:
// Routes
app.use('/api', usersRouter);
app.use('/books', booksRouter);
Database.js
const { MongoClient, ObjectId } = require('mongodb');
const uri = 'mongodb://localhost:27017';
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
// Declare db without assigning value
let db;
// Connect to the database
const connectDB = async () => {
if (!db) {
try {
await client.connect();
db = client.db('accountsdatabase');
} catch (error) {
console.error('Failed to connect to MongoDB:', error);
throw error;
}
}
return db;
};
Mongosh
mongosh
use (database)
mongodb extension
db.users.find().pretty()
Read more commands here.
book.js
Create an array for books. If you have data from a database you will create an array here:
const books = [
{...},
];
Create a route that sends a list of books in JSON format when someone visits the root path (/):
// Route to serve all books as JSON
router.get('/', (req, res) => {
res.json(books);
});
Create a route that lets you fetch a single book by its ID from a list of books:
// Route to fetch a single book by ID
router.get('/:id', (req, res) => {
const bookId = parseInt(req.params.id);
const book = books.find(b => b.id === bookId);
if (book) {
res.json(book);
} else {
res.status(404).json({ message: 'Book not found' });
}
});
browse.html
Define an async function that fetches a list of books from the server, organizes them by genre, and then displays them on the page:
// Fetch books from the server
const fetchBooks = async () => {
try {
const response = await fetch('/books');
const books = await response.json();
const genres = books.reduce((acc, book) => {
acc[book.genre] = acc[book.genre] || [];
acc[book.genre].push(book);
return acc;
}, {});
renderBooks(genres);
} catch (error) {
console.error('Error fetching books:', error);
container.innerHTML = '<p>Failed to load books. Please try again later.</p>';
}
};
book-details.html
Fetch the details of a specific book from the server using the book's ID, then update the webpage with the book's information:
// Fetch details for a specific book
fetch(`/books/${bookId}`)
.then(response => response.json())
.then(book => {
if (!book || book.message) {
bookDetailsContainer.innerHTML = '<p>Book not found</p>';
return;
}
// Populate book details
const { title, author, genre, year, description } = book;
const sanitizedTitle = title.replace(/[^a-zA-Z0-9]/g, '').replace(/\s+/g, '');
const bookImageUrl = `bookimages/${genre.toLowerCase()}/${sanitizedTitle}.jpg`;
document.getElementById('book-title').textContent = title;
document.getElementById('book-author').textContent = author;
document.getElementById('book-genre').textContent = genre;
document.getElementById('book-year').textContent = year;
document.getElementById('book-description').textContent = description;
document.getElementById('book-image').src = bookImageUrl;
document.getElementById('book-image').alt = title;
// Add Buy Now button logic
document.getElementById('buy-now-button').addEventListener('click', () => handleBuyNow(book, bookImageUrl));
})
.catch(error => {
console.error('Error fetching book details:', error);
bookDetailsContainer.innerHTML = '<p>Failed to load book details</p>';
});
});
users.js - POST /signup
Define a POST route at '/signup' for handling user registration:
// POST /signup
router.post('/signup', async (req, res) => {
const { name, email, password } = req.body;
try {
const db = await connectDB();
const usersCollection = db.collection('users');
// Check if the user already exists
const existingUser = await usersCollection.findOne({ $or: [{ name }, { email }] });
if (existingUser) {
return res.status(400).json({ message: 'User account already exists!' });
}
// Insert the new user with plain-text password
const result = await usersCollection.insertOne({ name, email, password });
res.json({ message: 'Sign-up successful!', userId: result.insertedId });
} catch (error) {
console.error('Error during sign-up:', error);
res.status(500).json({ message: 'Failed to sign up user', error });
}
});
Signup.html
Send a POST request to the server at the endpoint '/api/signup' to create a new user account:
// Sends a POST request to the server at '/api/signup' to create a new user account
fetch('api/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, password })
})
.then(response => response.json())
.then(data => {
alert(data.message);
})
.catch(error => {
console.error('Error:', error);
alert('There was an error with the sign-up.');
});
users.js - POST /login
Define a POST route at '/login' for user authentication and check if the provided username already exists in the database:
// POST /login
router.post('/login', async (req, res) => {
const { name, password } = req.body;
try {
const db = await connectDB();
const usersCollection = db.collection('users');
const user = await usersCollection.findOne({ name });
if (!user) {
return res.status(401).json({ success: false, message: 'User not found' });
}
if (user.password === password) {
res.json({ success: true, message: 'Login successful!', userId: user._id });
} else {
res.status(401).json({ success: false, message: 'Incorrect password' });
}
} catch (error) {
console.error('Error during login:', error);
res.status(500).json({ success: false, message: 'An error occurred during login', error });
}
});
Login.html
Send a POST request to the server at '/api/login' with the user's username and password to log in:
// Sends a POST request to the server at '/api/login' with the username and password
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, password })
})
.then(response => response.json())
.then(data => {
if (data.success && data.userId) {
// Store both userId and username in sessionStorage
sessionStorage.setItem('username', name);
sessionStorage.setItem('userId', data.userId);
alert('Login successful!');
window.location.href = 'index.html';
} else {
alert('Login failed: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('There was an error with the login.');
});
Index.html
// Retrieve User Data
const userId = sessionStorage.getItem('userId');
const username = sessionStorage.getItem('username');
users.js - POST /purchase
Define a POST route at '/purchase' to save details of a book purchase to the database:
// POST /purchase to save a purchased book
router.post('/purchase', async (req, res) => {
const { userId, bookId, purchasePrice, bookTitle, bookImageUrl } = req.body;
console.log('Received data for purchase:', { userId, bookId, purchasePrice, bookTitle, bookImageUrl });
// Validate fields
if (!userId || !bookId || !purchasePrice || !bookTitle || !bookImageUrl) {
return res.status(400).json({ message: 'Missing required data.' });
}
try {
const result = await addPurchase(userId, bookId, purchasePrice, bookTitle, bookImageUrl);
console.log('Purchase result:', result); // Log successful insertion result
res.status(200).json({ message: 'Purchase saved successfully', purchaseId: result.insertedId });
} catch (error) {
console.error('Error saving purchase in route:', error);
res.status(500).json({ message: 'Failed to save purchase', error: error.toString() });
}
});
database.js
// Add a purchase to the database
const addPurchase = async (userId, bookId, purchasePrice, bookTitle, bookImageUrl) => {
if (!ObjectId.isValid(userId)) throw new Error(`Invalid userId: ${userId}`);
try {
const db = await connectDB();
const result = await db.collection('purchases').insertOne({
userId: new ObjectId(userId),
bookId,
purchasePrice,
purchasedAt: new Date(),
bookTitle,
bookImageUrl,
});
return result;
} catch (error) {
console.error('Error adding purchase:', error);
throw error;
}
};
module.exports = { connectDB, addPurchase };
Buy.html
Send a POST request to the server at '/api/purchase' to save details of a book purchase:
// Sends a POST request to the server at '/api/purchase' to create a new purchase
try {
const response = await fetch('/api/purchase', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId,
bookId,
purchasePrice: parseFloat(purchasePrice),
bookTitle,
bookImageUrl,
}),
});
if (!response.ok) throw new Error(`Server error: ${response.status}`);
const data = await response.json();
if (data.message === 'Purchase saved successfully') {
alert(`Thank you for your purchase, ${cardName}!`);
setTimeout(() => window.location.href = 'index.html', 1000);
} else {
alert('Failed to save purchase. Please try again.');
}
} catch (error) {
console.error('Error saving purchase:', error);
alert('Error saving purchase. Please try again.');
}
users.js - GET /:userId/purchases
Define a GET route at '/:userId/purchases' to fetch a list of books that a specific user has purchased:
// GET /:userId/purchases to retrieve purchased books for a user
router.get('/:userId/purchases', async (req, res) => {
const userId = req.params.userId;
// Validate userId as a valid ObjectId
if (!ObjectId.isValid(userId)) {
return res.status(400).json({ message: 'Invalid user ID format.' });
}
try {
const db = await connectDB();
const purchasesCollection = db.collection('purchases');
// Use new ObjectId(userId) to correctly instantiate ObjectId
const purchases = await purchasesCollection.find({ userId: new ObjectId(userId) }).toArray();
res.status(200).json(purchases);
} catch (error) {
console.error('Error retrieving purchases:', error);
res.status(500).json({ message: 'Failed to retrieve purchases', error });
}
});
Index.html
Define a function, loadPurchasedBooks that fetches the purchase history for the user from the server and display the purchased books on the page:
const loadPurchasedBooks = (userId) => {
// Fetch the user's purchases from the server
fetch(`/api/${userId}/purchases`)
.then(response => {
if (!response.ok) throw new Error('Failed to fetch purchases');
return response.json();
})
.then(purchases => {
purchasedBooks.innerHTML = purchases.length
? purchases.map(createBookEntry).join('')
: '<p>No purchases found.</p>';
adjustContainerPadding(purchases.length);
})
.catch(() => {
purchasedBooks.innerHTML = '<p>Failed to load purchases. Please try again later.</p>';
});
};