Web Server and CRUD Operations Crash Course

Subscribe to my newsletter and never miss my upcoming articles

Hi guys!! I am back with another article. Today we are going to be going over CRUD operations. We will build a web server and go over the very basics of CRUD operations and learn a bit about RESTful services, APIs and endpoints. Note that we will not be using MongoDB or anything of the sort, we will just be executing these operations on an array.

So? Let's get to it shall we?

Initial Setup

  1. Ensure that you have node installed on your pc. You can download it here click me to download nodejs
  2. Next you can create a folder called crud-express-demo
  3. Run npm init -y (what this does is create a package.json file which is where all your dependencies will go when you install them)
  4. Next run npm i express joi@13.1.0. We are going to use Joi for input validation
  5. Run npm i -g nodemon to install nodemon globally on your machine. We use nodemon to keep the server running. As we make changes to our file, we won't need to keep running node index.js to run our server

Once you have all of that setup, we can get started

Setting up the server

  1. Create an index.js file in the root directory of the project
  2. In your index.js file, add the following code:
const express = require('express')
const app = express()

app.get('/', (req, res) => {
  res.send('Hello World')
})

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Listening on port ${port}`))

RESTful overview

Let's take a detour for a second. So, if you were not aware, we are building a RESTful web service. RESTful means Representational State Transfer and all you need to know really at this point is that it is a convention for building http services.

When you build any full-stack application you have a client and a server. The client needs to talk to the server to get data and save data based on various different user interactions. So what we do is expose an endpoint on our server which the client makes a request to in order to communicate with the http service. What is an endpoint you ask? An endpoint is pretty much a fancy name for a server URL and this URL is the means by and through which an API can access resources they need from a server to perform whatever task is at hand. I have assumed you know what an API is, but in laymans terms an API is simply a set of protocols and tools that enable two different applications to talk to one another. People are then able to write applications that can easily interface with one another.

So what is the difference between an API and an endpoint?

Well, an endpoint is the place/point at which two applications interact. It is a URL that enables the API to gain access to resources on a server. An API refers to all of the protocols that enable two systems to communicate with one another.

HTTP Methods

We use http methods to perform CRUD operations – create, read, update and delete. Usually you will want to update some stuff on a database, we use http methods. The methods we will be making use of are GET, POST, PUT, DELETE. GET retrieves stuff from a server. POST adds stuff on the server. PUT updates stuff and DELETE, well gets rid of stuff. Essentially we expose resources using an address and supportCRUD operations around those resources using standard http methods.

Back to business

Now that we have gone over the very basics of REST let's go back to our index.js file.

const express = require('express')
const app = express()

app.get('/', (req, res) => {
  res.send('Hello World')
})

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Listening on port ${port}`))

So, what is going on in this file? We are bringing in the express module which is a framework that helps us build http services more eloquently and in a more structured way than using the http module. When you call require, it returns a function which we assign to the constant express. We then store the express function which returns an object of type express in a constant called app.

Whenever we want to make a request we call an http method on our express object. So app.get(), app.post(), app.put(), app.delete(). Now all these methods take in two non-negotiable parameters, a path and a callback function that gets called when we get a response to that endpoint.

That get request has the root as the path and a callback function with req and res passed in as parameters. The req object represents the HTTP request and has properties for the request query string, parameters, body, HTTP headers, and so on. The res object represents the HTTP response that an Express app sends when it gets an HTTP request. Check out the documentation for more information.

So all we are doing is calling the get() method on our express object, passing in our path in the method along with a callback function and in there we are returning a response in the body of Hello World. If you run the server using nodemon index.js you should see Hello World printed on your screen.

The above is essentially how you define your routes.

In the next block we declare a constant called port and assign it the value process.env.PORT or 3000. Why are we doing this? When you are developing on your local environment, you are at liberty to choose whatever port you want. When you deploy your application to some hosting environment, the port is dynamically assigned, so you cannot rest assured that the static port you assign will be available.

PORT is an environment variable and all an environment variable is, is a variable that is part of the environment in which a given process runs – its value is set outside the application. process is a global object with a property called env so all process.env.PORT is doing is dynamically getting a port number when in production. That is all you need to know.

Next we call the listen() method on our express object app and which takes in a port and a callback function. In that callback function, we will basically log some stuff onto our console.

Create your dataset

Next we will create our dataset that we will be working with. You can really create whatever you like but I am going to create an array of objects with two properties for simplicity's sake.

const express = require('express')
const app = express()

const constructors = [
  {id: 1, name: "Mercedes"},
  {id: 2, name: "Red Bull Racing Honda"},
  {id: 3, name: "McLaren Renault"},
  {id: 4, name: "Racing Point BWT Mercedes"},
  {id: 5, name: "Renault"},
  {id: 6, name: "Ferrari"},
  {id: 7, name: "AlphaTauri Honda"},
  {id: 8, name: "Alfa Romeo Racing Ferrari"},
  {id: 9, name: "Haas Ferrari"},
  {id: 10, name: "Williams Mercedes"},
]

app.get('/', (req, res) => {
  res.send('Hello World')
})

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Listening on port ${port}`))

I have created an array of formula 1 constructors each with an ID and their names.

Note: 'constructors' here has absolutely nothing to do with programming (just in case you are not a F1 fan, constructor basically means team)

Routes

We want to define a few routes. We want to find constructors by their ID, retrieve all constructors, delete a constructor, update a constructor, create a constructor. So let's define all of these routes from the get go:

const express = require('express')
const app = express()

const constructors = [
  {id: 1, name: "Mercedes"},
  {id: 2, name: "Red Bull Racing Honda"},
  {id: 3, name: "McLaren Renault"},
  {id: 4, name: "Racing Point BWT Mercedes"},
  {id: 5, name: "Renault"},
  {id: 6, name: "Ferrari"},
  {id: 7, name: "AlphaTauri Honda"},
  {id: 8, name: "Alfa Romeo Racing Ferrari"},
  {id: 9, name: "Haas Ferrari"},
  {id: 10, name: "Williams Mercedes"},
]

app.get('/', (req, res) => {
  res.send('Hello World')
})

/**
 * GET /api/constructors
 * Retrieve all constructors
 */
app.get('/api/constructors', (req, res) => {
  // some logic
})

/**
 * GET /api/constructors/:id
 * Retrieve a specific constructor by ID
 */
app.get('/api/constructors/:id', (req, res) => {
  // some logic
})

/**
 * POST /api/constructors
 * Create a new constructor
 */
app.post('/api/constructors', (req, res) => {
  // some logic
})

/**
 * PUT /api/constructors/:id
 * Update an existing constructor by ID
 */
app.post('/api/constructors/:id', (req, res) => {
  // some logic
})

/**
 * DELETE /api/constructors/:id
 * Delete a constructor
 */
app.delete('/api/constructor/:id', (req, res) => {
  // some logic
})

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Listening on port ${port}`))

At this point, your application should be looking a little something like this. I have added various different routes. I hope you are still following. Let's add some logic in these routes.

Postman

We are going to be using POSTMAN to call our endpoints, so either install it using this link or add it to your extensions on chrome

Adding logic in your routes

GET Method

app.get('/api/constructors', (req, res) => {
  res.send(constructors)
})

This method is pretty straight forward, you are sending an http response that contains the array of constructors. Let's call this endpoint on postman.

  1. Start your server
  2. Head to Postman
  3. Your screen should resemble the following and you should get a list of the constructor objects

Screenshot 2020-10-02 at 19.25.16.png

So that route works. Onto the next one!!

app.get('/api/constructors/:id', (req, res) => {
  const constructor = constructors.find(c => c.id = req.params.id)
  if(!constructor) return res.status(404).send('The constructor with that ID cannot be found')

  res.send(constructor)
})

What is happening here? We are calling the find() method on our constructor array which takes in a function that returns a boolean value. We are essentially saying find the object in our array whose id property is the same as the id passed in the URL. What is params anyway? As stated in the official docs: params is an object containing properties mapped to the named route “parameters”. For example, if you have the route /user/:name, then the “name” property is available as req.params.name.

In the following all we are doing is returning a 404 http status and sending a message to the user explaining that a constructor with that ID does not exist.

Note: see http status codes here

On the last line, we send the constructor in the HTTP response.

POST Method

app.post('/api/constructors', (req, res) => {
  const constructor = {
    id: constructors.length + 1,
    name: req.body.name
  }
  constructors.push(constructor)
  res.send(constructor)
})

So, here we have our post method where we are adding a new constructor to our set of records. All we are doing is defining the object we want to add in the array. We set the id to the current length of the array plus one (usually the ID would be generated automatically when you add a new record). We set name to req.body.name. Now in order for req.body.name to work, we need to enable parsing of JSON objects in the body of the request because the feature is not present by default. So add the following line of code after your imports or anywhere before your routes: app.use(express.json()). So express.json() returns a piece of middleware which we call app.use to use in the request process pipeline. Without that, we cannot use parse any JSON objects in the request body.

After this, we push the object into the array and send it in the HTTP response. Let's go to Postman and create our own F1 constructor and add it to the array.

First you need to select POST on the drop down on the left of the bar where you enter the URL. Click on body which is where you will type in the JSON object. Click raw and select JSON in the drop-down (see image below)

Screenshot 2020-10-02 at 23.17.44.png

Next you can type the name of your F1 team and then click on send. You should end up with something that looks similar to the image below:

Screenshot 2020-10-02 at 23.22.33.png

PUT Method

Let's update an existing record.

app.put('/api/constructors/:id', (req, res) => {
    const constructor = constructors.find((c) => c.id === +req.params.id)
    if (!constructor)
        return res.status(404).send('The constructor with that ID was not found')

    constructor.name = req.body.name
    res.send(constructor)
})

In the above block of code, we are finding a course in the array with anid that matches the req.params.id. We also include a return statement such that sends a 404 HTTP status if the requested object is not found and we specifically return it so that the rest of the code in the function does not get executed. We then set the course name to the name property passed in the request body and then finally sent an HTTP response containing the updated course.

Note that req.params.id returns a string, so we have to convert it into a number if we are going to use the equal value and equal type (===) operator. You can either do the following to convert it parseInt(req.params.id) or you can simply add a plus in front of it as we did above. Both methods achieve the same things.

Your Postman should look a little something like this:

Screenshot 2020-10-02 at 23.42.52.png

You will notice that I changed "Mercedes" to "Mercedes AMG Petronas".

DELETE Method

Last but not least our delete method.

app.delete('/api/constructors/:id', (req, res) => {
  const constructor = constructors.find(c => c.id === +req.params.id)
  if(!constructor) return res.status(404).send('A constructor with that ID could not be found')

  const index = constructors.indexOf(constructor)
  constructors.splice(index, 1)

  res.send(constructors)
})

So, what's going on here? We find a course with a matching id. We then find the index of that specific course in the array of objects and store it in the constant index. We then call the splice() method on the array to remove the item at that specific index followed by returning an HTTP response with the updated array.

Your Postman should look a little something like this:

Screenshot 2020-10-02 at 23.47.50.png

We removed the object with the id = 3.

Validation

You should never, ever trust input from the client. For this exact reason, we need to validate input from the client. Earlier we installed a module called Joi, now we will make use of it.

The only places we need to insert validation is in our PUT and POST methods.

First we are going to bring in joi by adding const Joi = require('joi') at the top of our code.

You can then add this block of code at the beginning of your callback function inside your PUT method:

    const schema = {
        name: Joi.string().min(3).required(),
    }
    const result = Joi.validate(req.body, schema)

    if (result.error) return res.status(400).send(result.error)

What's going on here? We define a schema, which is essentially an object with the properties we want to validate. This schema will contain all of the properties we want to validate. Because our only have the name input, that is the only key:value pair in our object. We explicitly want the name property value to be a string that has a minimum of three characters and is a required value. We then call the validate method and pass in the request body and the schema. This will return an object which we store in a constant called results.

In the case where we have an error, an object will be returned and this object will include the error message we want to show to the user. Let's quickly have a look at what this object looks like:

{
    "isJoi": true,
    "name": "ValidationError",
    "details": [
        {
            "message": "\"name\" is required",
            "path": [
                "name"
            ],
            "type": "any.required",
            "context": {
                "key": "name",
                "label": "name"
            }
        }
    ],
    "_object": {}
}

I intentionally left the name property out and this is the object that was returned. Now this is not useful at all to the client. So let's change our code to return a meaningful error message. What we really want is the message property inside the details array. So we alter our code in the following way:

if (result.error) return res.status(400).send(result.error.details[0].message)

The error message we receive now instead of that huge object is: "name" is required. Much cleaner and more meaningful right?

Once you change that, your code should now look like this:

app.post('/api/constructors', (req, res) => {
    const schema = {
        name: Joi.string().min(3).required(),
    }
    const result = Joi.validate(req.body, schema)

    if (result.error) return res.status(400).send(result.error.details[0].message)

    const constructor = {
        id: constructors.length + 1,
        name: req.body.name,
    }
    constructors.push(constructor)
    res.send(constructor)
})

We can make this code a little bit cleaner by doing some array de-structuring. Instead of using result.error all the time, it is possible for us to just use the error property by just doing some array de-structuring. The change would look a little something like this:

    const {error} = Joi.validate(req.body, schema)

    if (error) return res.status(400).send(error.details[0].message)

Adding validation for the PUT method is exactly the same as above so I won't repeat it.

Conclusion

So lets recap what exactly happened here. We started off by building a basic web server. We then added some routes to the web server where we passed into out http methods some paths and callback functions which handled the requests to the endpoints. Next we added some CRUD operations and we validated client input using Joi. You've learned about RESTful services, the very basics, APIs, endpoints and CRUD operations. Not too bad for one article huh?

We have now come to the end of our article. If you got this far into the article, thank you!!! I hope this helped you gain a solid understanding of the basics. If this really helped you, share it with someone.

See you next time!

No Comments Yet