Creating a Single-File Node.js API with Express, MongoDB, and EJS Integration
Tags: node, javascript, webdev, mongodb
Published: 2023-11-10 16:23:27

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](https://eneaslari.com/2bed3247-8160-49d0-b38b-6a53affab36e.PNG "singlefilenodejs")

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](https://eneaslari.com/6f8ad02a-7563-48ec-938a-cdf208845de2.PNG "npminit")

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](https://eneaslari.com/f71c2822-6e0c-4039-ab16-aedfd59a3aee.PNG "package.json")

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](https://expressjs.com/ "expressjsurl")

`npm install express --save `

#### [mongoose.js](https://mongoosejs.com/ "mongoose") 

`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](https://www.npmjs.com/package//nodemon "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](https://eneaslari.com/f540b610-5dc6-4bd4-865c-f4bec09e3e87.PNG "serrver.js")

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

```javascript

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.

```javascript 
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.

```javascript
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](https://eneaslari.com/9b333138-04d6-4c65-9bd6-d548916e4480.PNG "create")

#### Get all products

![alt text](https://eneaslari.com/4e4fc1de-ea3f-4b50-aab6-7d673edc953c.PNG "getall")

#### Get product by Id

![alt text](https://eneaslari.com/717195ae-8e1f-4504-9a34-46efa67c8075.PNG "getbyid")

#### Update product by Id (Lets change name and price)

![alt text](https://eneaslari.com/a8ced9cf-9790-4a4e-a388-3ad4d96c0a55.PNG "update")

#### Delete product by Id

![alt text](https://eneaslari.com/6dc3a8f9-1b7d-4b2c-b339-d5385b7ec2c8.PNG "update")

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:

```sh
npm install ejs
```

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

```javascript
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:

```xml
<!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](https://eneaslari.com/c669a920-2d92-46a0-bac0-e5e2d2a115ed.PNG "browser")

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