Creating a Single-File Node.js API with Express, MongoDB, and EJS Integration

Creating a Single-File Node.js API with Express, MongoDB, and EJS Integration

Today, I sprang out of bed with a spark of creativity and the urge to shake things up a bit. So, I thought, why not craft a Node.js API all in one single file? That's the beauty of Node.js, isn't it? It's so flexible and doesn't box you in with strict rules on how to arrange your project. Sure, some might say it's not the "right way," but hey, when you're the architect of your creation, who's to say what's right or wrong? Your code, your rules!

Hey there! I'm usually on the hunt for the absolute best in architecture and the finest tools out there. But sometimes, it's liberating to step outside those boundaries. Let's toss the rulebook out the window for a change and take the road less traveled—the 'wrong' way. Who knows what creative solutions we might discover?

Sounds like a plan! Go ahead and create a new folder to house our compact Express server adventure. I see you've named yours singlefilenodejs—I love that it gets straight to the point. Let's get to it!

alt text

Absolutely! Let's get your project up and running. Here are the steps to initialize your Node.js project:

  1. Open your terminal or command prompt.
  2. Navigate to the singlefilenodejs folder that you created.
  3. Run the command npm init. This will create a package.json file with default values.

This package.json file will serve as the starting point for your project setup and will manage your project's dependencies. Now you're all set to start turning your project idea into reality!

alt text

Great! With your package.json in place, you're on the right track. This file is like the blueprint for your project, detailing all the dependencies and metadata.

alt text

Slip in "type": "module", right beneath the "main": "server.js", line. This tweak will enable you to utilize import statements within your server.js file.

Alright, we'll aim for simplicity by limiting ourselves to the bare minimum number of packages. This way, our project remains streamlined and efficient.

Lets install nessecary packages

express.js

npm install express --save

mongoose.js

npm install mongoose --save

Let's install Nodemon to avoid having to manually restart our server every time we make a change to our code. It's a handy tool that will save us a lot of time and effort during development!

nodemon

npm install --save-dev nodemon

Alright, let's fire up the simplest possible Express server using just four lines of code. It's quick and easy!

alt text

Let's go ahead and link our application with a MongoDB database. This will be our next step in setting up the backend.


import express from 'express'
import mongoose from 'mongoose'

const app = express()
const PORT = process.env.PORT || 5000

// Connect to MongoDB
try {
    const conn = await mongoose.connect('mongodb://127.0.0.1:27017/singlefilenodejsdb', {})
    console.log(`MongoDB Connected : ${conn.connection.host}`)
} catch (error) {
    console.log(`Error:${error.message}`)
    process.exit(1)
}

app.listen(PORT, console.log(`Server is running on port ${PORT}`))

Moving on, we'll define and create a Mongoose model for our products. This model will structure the product data in our MongoDB database.

import express from 'express'
import mongoose from 'mongoose'

const app = express()
const PORT = process.env.PORT || 5000

app.use(express.json()); // for parsing application/json

// Connect to MongoDB
try {
    const conn = await mongoose.connect('mongodb://127.0.0.1:27017/singlefilenodejsdb', {})
    console.log(`MongoDB Connected : ${conn.connection.host}`)
} catch (error) {
    console.log(`Error:${error.message}`)
    process.exit(1)
}

// Define a schema
const productSchema = new mongoose.Schema({
    name: String,
    price: Number,
    description: String,
});

// Create a model
const Product = mongoose.model('Product', productSchema);

app.listen(PORT, console.log(`Server is running on port ${PORT}`))

Now that we have our model in place, we're all set to create CRUD (Create, Read, Update, Delete) endpoints for our application.

import express from 'express'
import mongoose from 'mongoose'

const app = express()
const PORT = process.env.PORT || 5000

app.use(express.json()); // for parsing application/json

// Connect to MongoDB
try {
    const conn = await mongoose.connect('mongodb://127.0.0.1:27017/singlefilenodejsdb', {})
    console.log(`MongoDB Connected : ${conn.connection.host}`)
} catch (error) {
    console.log(`Error:${error.message}`)
    process.exit(1)
}

// Define a schema
const productSchema = new mongoose.Schema({
    name: String,
    price: Number,
    description: String,
});

// Create a model
const Product = mongoose.model('Product', productSchema);

// CREATE a new product
app.post('/api/products', async (req, res) => {
    try {
        const product = new Product(req.body);
        await product.save();
        res.status(201).send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});

// READ all products
app.get('/api/products', async (req, res) => {
    try {
        const products = await Product.find({});
        res.status(200).send(products);
    } catch (error) {
        res.status(500).send(error);
    }
});

// READ a single product by id
app.get('/api/products/:id', async (req, res) => {
    try {
        const product = await Product.findById(req.params.id);
        if (!product) {
            return res.status(404).send();
        }
        res.send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});

// UPDATE a product
app.patch('/api/products/:id', async (req, res) => {
    try {
        const product = await Product.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
        if (!product) {
            return res.status(404).send();
        }
        res.send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});

// DELETE a product
app.delete('/api/products/:id', async (req, res) => {
    try {
        const product = await Product.findByIdAndDelete(req.params.id);
        if (!product) {
            return res.status(404).send();
        }
        res.send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});


app.listen(PORT, console.log(`Server is running on port ${PORT}`))

Time to put our endpoints through their paces using Postman. It's the perfect way to see them in action!

Create a new product

alt text

Get all products

alt text

Get product by Id

alt text

Update product by Id (Lets change name and price)

alt text

Delete product by Id

alt text

Now that we've confirmed all our endpoints are functioning flawlessly, let's enhance our application by incorporating the capacity to render HTML pages using EJS.

Here's how you can set up HTML rendering at the root route with EJS in your Express server:

  1. Install EJS using npm.
  2. Set EJS as the view engine for your Express application.
  3. Create a view file for the root route.
  4. Render the view when the root route is accessed.

Here's how you can modify the existing server code to include EJS and render an HTML page:

First, install EJS:

npm install ejs

Then, add the following code to your server.js to set up EJS and render a page:

import express from 'express'
import mongoose from 'mongoose'

const app = express()
const PORT = process.env.PORT || 5000

app.use(express.json()); // for parsing application/json
app.set('view engine', 'ejs'); // Set EJS as the view engine
app.set("views", "./"); // Set the views folder as the root directory of server.js (the default is a folder named views)

// Connect to MongoDB
try {
    const conn = await mongoose.connect('mongodb://127.0.0.1:27017/singlefilenodejsdb', {})
    console.log(`MongoDB Connected : ${conn.connection.host}`)
} catch (error) {
    console.log(`Error:${error.message}`)
    process.exit(1)
}

// Define a schema
const productSchema = new mongoose.Schema({
    name: String,
    price: Number,
    description: String,
});

// Create a model
const Product = mongoose.model('Product', productSchema);

// CREATE a new product
app.post('/api/products', async (req, res) => {
    console.log(req.body)
    try {
        const product = new Product(req.body);
        await product.save();
        res.status(201).send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});

// READ all products
app.get('/api/products', async (req, res) => {
    try {
        const products = await Product.find({});
        res.status(200).send(products);
    } catch (error) {
        res.status(500).send(error);
    }
});

// READ a single product by id
app.get('/api/products/:id', async (req, res) => {
    try {
        const product = await Product.findById(req.params.id);
        if (!product) {
            return res.status(404).send();
        }
        res.send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});

// UPDATE a product
app.put('/api/products/:id', async (req, res) => {
    try {
        const product = await Product.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
        if (!product) {
            return res.status(404).send();
        }
        res.send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});

// DELETE a product
app.delete('/api/products/:id', async (req, res) => {
    try {
        const product = await Product.findByIdAndDelete(req.params.id);
        if (!product) {
            return res.status(404).send();
        }
        res.send(product);
    } catch (error) {
        res.status(500).send(error);
    }
});

// Serve your CSS as static files (if needed)
app.use(express.static('public'));

// Render HTML at the root route using EJS
app.get('/', (req, res) => {
    res.render('index', { title: 'Single File Server' });
});


app.listen(PORT, console.log(`Server is running on port ${PORT}`))

Create a file called index.ejs. This will be your HTML template for the root route. Here's an example of what index.ejs might contain:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= title %></title>
  </head>
  <body>
    <h1>This is the home page of our single file express app</h1>
  </body>
</html>

With this setup, when you navigate to the root route (http://localhost:5000/), Express will render the index.ejs view, injecting the title variable into the template.

If you have any CSS styling, place your CSS files in a directory called public which you will have to create at the root of your project. Express is set up to serve files from this directory as static files.

if you open your browser at http://localhost:5000/ you will see:

alt text

And there we have it, friends! I hope you had a good time with our little adventure.

More to read