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.

Here more info.

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 here

The 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>';
});
};