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!
Absolutely! Let's get your project up and running. Here are the steps to initialize your Node.js project:
- Open your terminal or command prompt.
- Navigate to the
singlefilenodejs
folder that you created. - Run the command
npm init
. This will create apackage.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!
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.
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!
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
Get all products
Get product by Id
Update product by Id (Lets change name and price)
Delete product by Id
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:
- Install EJS using npm.
- Set EJS as the view engine for your Express application.
- Create a view file for the root route.
- 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:
And there we have it, friends! I hope you had a good time with our little adventure.