Emeka Okezie
Posted on October 26, 2020
In this project, we will be creating a simple API that would allow people Update, Delete, Create, and Subscribe to a social media account. We will do this by coding the data the users will be interacting with within our social media database and how it goes about manipulating them depending on what a user wants to do.
Project Requirements and Dependencies
Important Prerequisite: Make sure you already have MongoDB installed and set up on your machine before starting this tutorial. Here is a link to a guide that MongoDB provides on their website: MongoDB Installation
After installing MongoDB, the next step would be the opening of your command line, then create a new directory for the project we will be working on using this command mkdir directory_name
this would quickly create your new directory, then you would need to be in the newly created directory by using the following command cd directory_name
this would give you access to the directory you have created.
In this project, we would be working with the express generator
to quickly create our application skeleton. For earlier Node versions, install the generator as a global npm package and then launch it using the following command npm install -g express-generator
. This would automatically install the express generator
globally on your local machine, after installation, you would be required to type express
in your command line, this will create all the required files to set up your express app skeleton
After creating our express app, you would be required to delete the views and public
folders as well as the bin
folder, since all we are creating is a simple REST-API, we won't be needing to make use of them, also, we will be required to take out the line of codes for the view engine set up and the middleware for joining the public directory to our app.js file.
Next, we will be installing Mongoose, which connects our application to the MongoDB Database using this command npm install mongoose
then dotenv and nodemon
for setting up our environment variables and restarting our local server respectively, after we saved our files with the .JS
extension. this will be installed using the following command npm install --save-dev dotenv nodemon
.
Note: --save-dev from above will save development-only dependencies without saving them to production
dotenv
will allow us to pull in environment variables from a .env
file. When we ran the express
command earlier it created a package.json
file. In this file under the scripts, we want to replace that ‘test’ script with our own script that starts our server with nodemon
:
Setting up Our Local Server
Running the express
command in the command line, after installing express-generator
automatically created an app.js
file. Once created, we will need to require all our packages at the top of our app.js
file, for the file created using the express
command, you will already have the express dependency declared at the top of the app.js
file, all you will need to do is require the dotenv and mongoose
packages.
When we want to test and make sure our server is working, we will run this function to listen on port 3000 and log a string if it's successful:
app.listen(3000, () => console.log('server started'))
Connecting to Our Database
Before running the database connection, first, you will be required to pull in the dotenv
package you had initially installed, just the same way you did for express and mongoose like this const dotenv = require('dotenv').config()
. The .config()
function automatically configures the dotenv
package to be used in your application entry point.
This next chunk of code in our app.js
file starting with const CONFIG
is how we will be connecting to the database. I’ll explain it line by line:
The OPTIONS
object is actually optional for the purpose of this API, mongoose.connect(CONFIG.uri, CONFIG.OPTIONS)
allows us connect to the database, you may be wondering the DATABASE_URL
, this is the location where we want to store our database, which has been identified in the .env
file created in the root folder of our API. If you haven't created a .env
file, now will be a good time to go about this and set the DATABASE_URL
there:
Moving on, we have to set db
to mongoose.connection
as a syntactically easier way to reference our database. Next, we’ve defined what happens when the database connection is successful, which is to log the string of connection to the database on our terminal.
Testing the Database Connection
At this point, we can now test out our connection to the database to make sure everything is working smoothly. In our terminal, let’s go ahead and start our database by typing mongod and then in another terminal tab let’s start our server with npm run dev
. If all went well, we should get these messages after we start our server:
Cool! We’ve successfully started our server and our database appears to be operational.
Setting up our Server to Accept JSON
In our same app.js file we want to tell Express
that it should accept JSON. Lets put this line of code in between our ‘database is open’ line of code and the ‘listen on port 3000’ line:
The .use
is a middleware that allows you to run code when the server gets a request but before it gets passed to your routes. So in this instance, we’re telling Express
to accept JSON as the data format.
Creating our Route File
Inside our directory, you will notice you already have a routes
folder in it, we also have an index.js
file, this is where we will define how the server should handle the data when it receives a GET, POST or PATCH/UPDATE request.
But before we start creating those, let's switch back to our app.js file and tell the server that we now have routes that it needs to handle and use. These next two lines of code are actually the last ones we need for our app.js file.
var indexRouter = require('./routes/index');
app.use('/', indexRouter);
Here’s the full server.js file with the new lines of code added:
If something is missing or incorrect, right now is a great checkpoint to make sure you’ve got everything updated with the above. Give yourself a pat on the back for getting this far! We’re getting onto the fun stuff next…
Setting up Our Routes
Now that our server file is all set up, we will now switch gears to our new index.js
routes file. To start, let us require Express
and define the express.router()
function on a variable called router. While we’re at it let’s add our module export function as well at the very bottom:
Before we get into the code, let’s get an understanding of what we exactly want to do with our routes. Since we’re trying to see and artificially create new subscribers on Social App, we need this set of criteria:
Route for getting all subscribers
Route for getting one subscriber
Route for creating one subscriber
Route for updating one subscriber
Route for deleting one subscriber
Now let’s start each of the bullet points above with the basic framework depending on if its a GET, POST, or PATCH request:
To get this fully set up, we could be creating this API using the MVC method without a View
, by this, I mean since it's just an API we won't be needing to render page here. We will be creating a Controller
folder which will handle all of our routing functions, then we will be exporting these functions to our index.js
file in the Routes
folder. First, you should have these lines of code in your index.js
folder for your routing.
Ignore all the errors, for now, we will sort out all of those very soon, you may go ahead in creating your controller folder and an indexController.js
file.
The one thing that might look weird is the PATCH method. The PATCH method is being used here instead of the more traditional PUT method because we only want to use this to update pieces of the subscriber’s information and not all the information of the subscriber.
You also may have noticed that we include /:id
in most of our parameters. This is for the routes that are requesting an action to be taken on a single subscriber thus we need that subscriber’s unique ID.
Testing Our IndexController File
In our indexController.js
file we will be writing a chunk of code, this is with the aim of testing our API to see if it's well connected using our routing. I will be needing to explain the code below, later on, right now our routing functionality is now been declared in the indexController.js
file then export to the index.js
file in the router folder
Below is where the indexController.js
file is being imported into the routing file.
What the above block of code means is this, in the indexController.js
file, we used a class-based method in exporting all our functions, then we created an arrow function called testingRoute
then assigned a response and require argument to it, afterward, we sent a simple JSON message 'Hello World'
using the res.send()
command in a try and catch block. What this does is to try the initial command in this function, in case there is an error, the catch block prints the error in the console, after this is done, we then export the functions inside the Class
to the router file.
In the index.js
file we then require the indexContoller.js
file and assigned it to a new variable called IndexController
, then using the GET
method, we will be calling out the content of the indexController.js
as shown in the code above.
Now for testing our API, I will be making use of Postman, you can download it from their official page Postman Installation. After you are done installing, open the Postman app, you will get the same page:
All you will be required to do is test the GET call to our Server
http://localhost:3000
using the Postman app, just click on the +
icon on the app to access a new tab, then copy this URL and paste in the input field, first you need to select the action as a GET request, then hit the Send button, this would print the 'Hello World'
text we sent through the indexController.js
file as shown below:
This means our API is working correctly! Ok so we’ve done a lot so far, let’s take a hydration break, and then we’ll get started with models.
Making the Models
Let’s go ahead and set up our model and the schema inside of our model. A schema is how our app defines what the data looks like and also sets up the document in MongoDB. If that sounds confusing, it’ll make more sense once we see what’s going on.
Let’s first start by creating a new folder called models. Inside of this folder, let’s create a new file called subscriber.js. The idea is that the model will handle how each and every subscriber, on an individual level, will look inside of our database. The ‘index’ routes handle sometimes multiple subscriber's requests such as Get All Subscribers route. It’s an important thing to mention as verbiage is important when naming files in projects.
In this new file, we want to first require Mongoose since we will be using their schema models:
const mongoose = require('mongoose')
After we require mongoose, we’ll start by defining our schema
const mongoose = require('mongoose')
const subscriberSchema = new mongoose.Schema({})
Inside of this javascript object will be all of the keys for the different properties of our subscriber. These keys will include name, subscribedChannel and subscribeDate
. We essentially want to tell our database what to expect from each one of these keys such as their type, if they’re required and if a default value should be applied.
The type and required properties should be pretty self-explanatory. These are defining the expected schema type (a String and Date in this case) as well if that key is required upon entering information for a new subscriber.
One thing to note about subscribeDate
, we set the type to Date instead of String since we will be expecting a date from the user. If no date is provided then we default it to the current date by using Date.now
.
Moving on, the last line of code we want to write in our schema is the module.exports. This will allow us to use and interact with the database using our schema. Mongoose has a special way of exporting models utilizing mongoose.model() that takes two properties:
module.exports = mongoose.model('Subscriber', subscriberSchema)
‘Subscriber’ is the name we want to give the model in our database and then the next one is the schema that corresponds to that model which is our subscriberSchema.
That’s it for our model! Let’s take a look at the finished schema to make sure we’ve got it all:
Creating and Getting Subscribers
Now that we have our model setup with a schema for our database, let’s require it in our indexController.js controller file below where we required router
const Subscriber = require('../models/subscribers')
We have our test response from earlier when we sent ‘Hello World’ to the server but we can go ahead and delete that since we want to actually tell that route to Get All Subscribers.
The first thing we’ll need to do after we get rid of our old res.send('Hello World') line in the indexController.js
file, is wrap the function in a promise with a try/catch statement, just like we already did previously:
Inside of our try statement we want to get all of the subscribers from our model. So we want to set a new variable called subscribers to that model with a .find() method applied to our Subscriber model.
As the name implies, the find() Mongoose method works by returning all associated subscriber objects that meet its criteria. Since we’re returning all subscribers, we just leave the parentheses blank since we want all the subscribers:
After that line, we then want to send a response with the data of our subscribers variable we just created in the form of JSON. Finally, in our catch statement we want to just catch any errors that may occur and have it sent to the user as a 500 error in JSON format:
Now that we have our route to send us all the subscribers in our database, we need to code a way for us to actually add a subscriber into our database. So, lets move onto our Create One Subscriber controller so we can enter data about a subscriber:
You can see it's somewhat similar to our Get All Subscribers controller except for a few important differences. For one, we’re no longer doing a GET call on our database but a POST which will allow us to push data to our database.
On this line:
const subscriber = new Subscriber({...
we are creating a variable that will be assigned to a new Subscriber from our model that we created earlier. If you recall, we require a name, subscribedChannel and subscribeDate
properties for a new subscriber.
These next two lines of code:
name: req.body.name,
subscribedChannel: req.body.subscribedChannel
We’re telling our controller to save the request made from a user’s input of a new Subscriber name property and subscribedChannel
property. Our subscribeDate doesn’t need to be defined because it will default automatically to the date/time that this database entry is made.
The try and catch
statements should look familiar. We’re instead using a .save()
Mongoose method instead of find()
because this is how we will tell the database that we want it to hold the information a user passes to us through this controller function.
Lastly:
...
res.status(201).json(newSubscriber)
} catch (err) {
res.status(400).json({ message: err.message })
}
We’re sending the user response with a success status of 201 and to pass our new subscriber back to us as JSON. The catch is just like our Get All Subscribers controller except we pass a 400 error since this would be a user error for passing us bad data.
To test all of this, in our index.js
route file, after requiring the indexController.js
file, for the Get all Subscribers route, this would be written out like this router.get('/', IndexController.getAllSubcribers)
since it is a get request to all the Subscribers, for the create a single Subscriber, since it is a POST action router.post('/create', IndexController.createSingleSubcriber)
, mind you at this point you have your IndexController called up in the index.js
file like we do in the code below:
...
IndexController.getAllSubcribers and
IndexController.createSingleSubcriber
This is just a way of targeting the functions inside the indexControler.js
file, remember we recently declared these two functions to get all subscribers and create a single subscriber, we do this by appending the functions of the indexController.js
file to the IndexController
variable which was declared in the route file const IndexController = require('../controller/indexController')
.
Now to actually test all we have done, we will be create a new subscriber using the create
route we just created, remember it is a POST action. we will do this by typing the following url in our Postman application. http://localhost:3000/create
, the /create/ path was declared in our
index.js` route file. Before hitting the create route, we need to do a quick set up in Postman to be able to pass in raw JSON data
First, we need to select the Body(in green)
, then the raw option, afterward we then select JSON from the drop-down option, this would make POSTMAN know we are about passing a JSON data,
Once you do this as shown above you can then manually type this in the same format in the input space provided in POSTMAN
{
"name": "Robert",
"subscribedChannel": "Bennetts Channel"
}
If after you are done with this and hit the submit button, you should get this output:
We just created a new user, the user now has his own unique ID, If everything went well, our response when we click ‘Send Request’ should look like the above. We can see that we received our 201 success status at the very top along with our Subscriber object at the bottom with all the information we just plugged in.
Again, subscribeDate is set automatically to the date of creation so we don’t need to manually set it.
At this point, we can easily get the number of subscribers in our database by just hitting the http:localhost:3000/
URL, but this time with a GET action, after hitting the send button, POSTMAN would print out all SUbscribers in the Database.
Get Single User
The first line on this code looks pretty familiar already, in the try and catch
block, we used a MongoDB method of findById
to target a particular ID from our Database, where (req.params.id)
The req.params property is an object containing properties mapped to the named route “parameters”. For example, if you have the route /student/:id, then the “id” property is available as req.params.id. This object defaults to {}. Now, we are mapping through the Subscriber Database to search for a particular ID,findUser == null
implies that if the subscriber is not in the Database, the next command in the function is to return a status code of Error 404
with the message of 'Cannot find the subscriber'
else res.status(201).json(findUser)
simply implies that the subscriber found should be printed in JSON with the status code of 201, which means that everything is working well.
At this point your index.js
route file should be looking like this:
To get a single Subscriber, we need to first GET all the Subscribers, using the URL http:localhost:3000/
in your POSTMAN, then copy a Subscriber's unique ID, after doing this you can now confirm if your function is working well by hitting this link http:localhost:3000/single/theUserId
this should get you the single User with the status code of 201.
Delete Subscriber
Just the same way we did for the GET single Subscriber by using the MongoDB command of findById
, in this case, we had to do a little different thing, after finding the users by their ID using const findUser = await Subscriber.findByIdAndDelete(req.params.id)
the next action was confirmed is the User/Subscriber really exist in the Database, if so, remove this user using the following command Subscriber.remove()
where Subscriber is the model the user is located and remove() is a MongoDB function to remove a data from the database, after you are done, your code should look just like what we have above, if the subscriber is not in the Database, the next command in the function is to return a status code of Error 404
with the error message. After this has been done successfully, your delete route in the index.js
route file should and update and would look like this:
Also if you try deleting a Subscriber, after selecting their ID on POSTMAN, you should get this result too:
--NB: This has got to be a DELETE action also like you did for GET to get all Subscribers or single Subscriber--
Patch/Update a Subscriber
Our Update Subscriber route is the very last thing we need to write for this application to be fully functional! Ok so before we get into the code lets get a general idea of how updating will work in this case:
User updates just the name
User updates just the channel
User updates both name and channel
Or they mess up and it throws an error
Our requirements need for us to essentially check and see if any changes were made and if so, update them appropriately. Now onto the code:
Using the same method as wit the previous, the first line of code remains familiar,
...
const updateUser = await Subscriber.findByIdAndUpdate(req.params.id , {
name : req.body.name,
channel : req.body.subscribedChannel
}
In this case, we are using a method of findByIdAndUpdate, to map through the Database, and then if the particular that has been inputted is actually in the Database if so, we are targeting the name and subscribedChannel
from the Subscriber
model, and either update both fields or any one of them, if this operation is okay, print the JSON
message.
If your code is correct and is the same as the one above, the next operation would be updating your index.js
route file, which would finally look like this:
Afterward. we can now test this route with the POSTMAN. first, we will get a single user using his Id and this link,http:localhost:3000/single/5f967dedbab34829a4eb83ee
. this will be a GET request, after we get this User, and update his/her record, we can then PATCH the user using this URL http:localhost:3000/update/5f967dedbab34829a4eb83ee
and his the Send button, we will get the result below:
You can confirm that the User details really got updated by either get just that user again or getting all the users as shown below:
Conclusion
Guys, we made it. I really hope this tutorial was helpful to some of you. We went over a lot of stuff so if you’re feeling overwhelmed then that's totally understandable. But realize we just made a pretty awesome backend piece of code that translates into so many different real-world applications. So big props to you for making it through it all!
The complete code can be found in the Master branch of my GitHub repo
If you ever got stuck or found something worth mentioning, go ahead and drop a message on Twitter or leave me a comment below.
Posted on October 26, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 15, 2021