Overview

...

Table of Contents

Express js

//import express
const express = require('express');
// define app
const app = express();
1
2
3
4

express basics

express ships with usefull http functions

  • .get()
  • .post()
  • .put()
  • .delete()
Function Use Example
.get()
.post()
.put()
.delete()

get()

Arguments

Example

  1. import express
  2. define app
  3. define routes/path
  4. listen
const express = require('express');
const app = express();

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

app.listen(3000, () => console.log('listening on port 3000');

1
2
3
4
5
6
7
8
9

app.listen() allows you to execute a callback when the application starts

add a second route

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

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

app.get( '/api/courses' , (req, res) => {
  //sample send an array of three numbers as a response
  res.send([1, 2, 3]);
});

app.listen(3000, () => console.log('listening on port 3000');

1
2
3
4
5
6
7
8
9
10
11
12
13
14

you can split up your code by moving your routes to seperate directories and importing them

Using parameters

Parameters are dynamic endpoints in your application

Attach parameters by appending them to the end of express path's

Example #1

Problem

  • we want to listen for the :id

Solution #1 - named base parameters

  • Attach :id to the end of the express path variable -- /api/courses/:id

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

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

app.get( '/api/courses' , (req, res) => {
  res.send([1, 2, 3]);
});

app.get( '/api/courses/:id' , (req, res) => {
  //respond with the request id
  res.send(req.params.id);
});

app.listen(3000, () => console.log('listening on port 3000');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Accessing Parameter variables

You can access the passed in value with the req.params object

Named based parameters

in the example above we access the req.params.id variable defined in /api/courses/:id

  • it is possible to have multiple parameters in a slot
    • /api/posts/:year/:month

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

app.get( '/api/courses/:posts/:year' , (req, res) => {
  //respond with the request id
  res.send(req.params.posts);
  res.send(req.params.year);
});

app.listen(3000, () => console.log('listening on port 3000');

1
2
3
4
5
6
7
8
9
10
11
12

output

// example url
// leadsapi.app/api/posts/2099/1

{
  year: "2099",
  month: "1"
}

1
2
3
4
5
6
7
8

Express allows query string parameters i.e. leadsapi.app/api/posts/2099/1?sort=name i.e. leadsapi.app/api/posts/2099/1?sortBy=name

Query string parameters

  • optional additions added to the query
  • return req.query

use query string parameters when you want to add optional data

name based = required i.e. /api/courses/:id query string = optional i.e. /api/courses/:id?sortBy=name


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

//sample data
const courses = {
  { id: 1, name: 'course1' },
  { id: 2, name: 'course2' },
  { id: 3, name: 'course3' },
}

//return full courses array
app.get( '/api/courses' , (req, res) => {
  //instead of reading req.params we read req.query
  res.send(courses);
});


//return queried course with a little bit of logic
app.get( '/api/courses/:id' , (req, res) => {
  //boolean determins if req.params.id exists
  courses.find(c => c.id === parseInt(req.params.id));
  // 404 http status code obj not found
    if (!course) return  res.status(404).send('The course with the given id was not found');

  res.send(course);
 
});

app.listen(3000, () => console.log('listening on port 3000');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

output

// example url
// leadsapi.app/api/posts/2099/1?sortBy=name

{
  year: "2099",
  month: "1"
}

1
2
3
4
5
6
7
8

Using environment variables

our app has access to the process variable

use case

  • assign a listening port in a node application
.....

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

now if the port changes a new port is logged

post()

Arguments


// mock data used in this sample
app.post('/api/courses', (req,res) => {
  const course = {
    id: course.length + 1,
    name: req.body.name
  };
  //push the array
  courses.push(course);
  // should return response from server in the body of the response
  res.send(course);
});

1
2
3
4
5
6
7
8
9
10
11
12
13

Example json body to send to endpoint

{
  "id" : 4,
  "name" : "newcourse"
}

1
2
3
4
5

It's always important to add some validation to make sure your server is recieving the correct values

Sample #1 : Simple validation

Validation flow generally follows the following pattern

  1. Define validation pattern
  2. Send a http response message

By modifying the above post function we want to check for the following

  • the name exists
    • !req.body.name
  • the name has more than three characters
    • req.body.name.length < 3

In javascript you can seperate conditional statements by using ||

app.post('/api/courses', (req,res) => {
  //Simple Validation
  //Here we check if the request body contains the name data OR if the length of the name object is too short i.e. Less than 3 characters
  if(!req.body.name || req.body.name.length < 3) {
    // 400 Bad Request
    res.status(400).send('Name is required and should be minimum of 3 characters')
    return;
  }
  
  const course = {
    id: course.length + 1,
    name: req.body.name
  };
  courses.push(course);
  res.send(course);
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

**Sample #2 : Using the joi package

For more complex validation you can use helpful frameworks such as the joi package joi - npm

  1. Install joi
  2. Import or Require the joi package

install joi

npm i joi
1
const express = require('express');
const app = express();
//require joi
const Joi = require('joi');
1
2
3
4

Using Joi to define schemas

Schemas are usefull to validate incomming objects properties

  • Type of object
    • string
    • number
    • array
    • boolean
    • etc..
  • properties
    • email
    • string
    • min - max characters

and so on

Defining a schema in joi

const Joi = require('joi');
 
const schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email({ minDomainAtoms: 2 })
}).with('username', 'birthyear').without('password', 'access_token');
 
// Return result.
const result = Joi.validate({ username: 'abc', birthyear: 1994 }, schema);
// result.error === null -> valid
 
// You can also pass a callback which will be called synchronously with the validation result.
Joi.validate({ username: 'abc', birthyear: 1994 }, schema, function (err, value) { });  // err === null -> valid
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

The above sample schema defines the following constraints:

  • username
    • a required string
    • must contain only alphanumeric characters
    • at least 3 characters long but no more than 30
    • must be accompanied by birthyear
  • password
    • an optional string
    • must satisfy the custom regix
    • cannot appear together with acces_token
  • access_token
    • an optional unconstrained string or number
  • birthyear
    • an integer between 1900 and 2013
  • email
    • a valid email address string
    • must have two domain parts e.g. example.com

Adding schema to our example

const Joi = require('joi');
const express = require('express');
const app = express();


//sample data
const courses = {
  { id: 1, name: 'course1' },
  { id: 2, name: 'course2' },
  { id: 3, name: 'course3' },
}

app.post('/api/courses', (req,res) => {
  //Step #1: define schema
  const schema = {
    name: Joi.string().min(3).required()
  };
  
  //Step #2: activate the schema on the POST data
  
  const result = Joi.validate(req.body, schema);
  //Debug logging
  console.log(result);
  
//Step #3: Check for error based on schema
  if(result.error) {
    res.status(400).send(result.error);
    return;
  }
  
  const course = {
    id: course.length + 1,
    name: req.body.name
  };
  courses.push(course);
  res.send(course);
});

app.listen(3000, () => console.log('listening on port 3000');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

Sample #3: accessing the results array

const Joi = require('joi');
const express = require('express');
const app = express();

const courses = {
  { id: 1, name: 'course1' },
  { id: 2, name: 'course2' },
  { id: 3, name: 'course3' },
}

app.post('/api/courses', (req,res) => {
  //Step #1: define schema
  const schema = {
    name: Joi.string().min(3).required()
  };
  const result = Joi.validate(req.body, schema);

  if(result.error) {
    //accessing one
    res.status(400).send(result.error.details[0].message);
    /* or you can access the whole result data and concatinate
    res.status(400).send(result.error.details);
    */
    return;
  }
  
  const course = {
    id: course.length + 1,
    name: req.body.name
  };
  courses.push(course);
  res.send(course);
});

app.listen(3000, () => console.log('listening on port 3000');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

put()

standard use cases

  • update data

Arguments

put() structure


app.put('/api/courses/:id', (req, res) => {
  ....
});

1
2
3
4
5

#Sample 1: using put()

In the following sample we aim to

  • look up course where id = :id
  • If course doesnt exist, return 404
  • Validate
  • If invalid, return 400 - bad request
  • Update course
  • Return the updated course
const Joi = require('joi');
const express = require('express');
const app = express();

const courses = {
    {
        id: 1,
        name: 'course1'
    },
    {
        id: 2,
        name: 'course2'
    },
    {
        id: 3,
        name: 'course3'
    },
}

app.put('/api/courses/:id', (req, res) => {
            // Look up course
            app.get('/api/courses/:id', (req, res) => {
                courses.find(c => c.id === parseInt(req.params.id));
                // Return error if the course doesnt exist
                if (!course) return  res.status(404).send('The course with the given id was not found');

                // Validate
                const schema = {
                    name: Joi.string()
                        .min(3)
                        .required()
                };
                const result = Joi.validate(req.body, schema);
                // If invalid, return 400 - Bad request
                if (result.error) {
                    res.status(400)
                        .send(result.error.details[0].message);
                    return;
                }
              
                // define the new course object
                course.name = req.body.name;
                // Update course
                res.send(course);
                // Return the updated course

            });

            app.listen(3000, () => console.log('listening on port 3000');


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

#Sample 1 - A: Refactoring our code to be reusable

  • whenever you can follow DRY principles
    • D : Dont
    • R : Repeat
    • Y : Yourself

we will abstract our validation logic to a reusable function

const Joi = require('joi');
const express = require('express');
const app = express();

const courses = {
    {
        id: 1,
        name: 'course1'
    },
    {
        id: 2,
        name: 'course2'
    },
    {
        id: 3,
        name: 'course3'
    },
}

app.put('/api/courses/:id', (req, res) => {
            app.get('/api/courses/:id', (req, res) => {
                courses.find(c => c.id === parseInt(req.params.id));
                if (!course) return  res.status(404).send('The course with the given id was not found');

              // Use our reusable validation function
              const result = validateCourse(req.body);
              // Optional : Object see snippet bellow 
              
               if (result.error) {
                    res.status(400)
                        .send(result.error.details[0].message);
                    return;
                }
              
                course.name = req.body.name;
                res.send(course);
            });
  
            //define a reusable function to validate courses
            function validateCourse(course) {
                const schema = {
                    name: Joi.string()
                        .min(3)
                        .required()
                };

                // return result to the calling function
                return Joi.validate(course, schema);
            }
          
            app.listen(3000, () => console.log('listening on port 3000');


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

Object destructuring

You can keep your code cleaner by using object destructuring

var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20

[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]

({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20


// Stage 3 proposal
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Using in our api

const Joi = require('joi');
const express = require('express');
const app = express();

const courses = {
    {
        id: 1,
        name: 'course1'
    },
    {
        id: 2,
        name: 'course2'
    },
    {
        id: 3,
        name: 'course3'
    },
}

app.put('/api/courses/:id', (req, res) => {
            app.get('/api/courses/:id', (req, res) => {
                courses.find(c => c.id === parseInt(req.params.id));
                if (!course) return  res.status(404).send('The course with the given id was not found');
                
              // Object Destructuring
              const { error } = validateCourse(req.body); // result.error
              
               if (error) {
                    res.status(400)
                        .send(error.details[0].message);
                    return;
                }
              
                course.name = req.body.name;
                res.send(course);
            });
  
            //define a reusable function to validate courses
            function validateCourse(course) {
                const schema = {
                    name: Joi.string()
                        .min(3)
                        .required()
                };

                // return result to the calling function
                return Joi.validate(course, schema);
            }
          
            app.listen(3000, () => console.log('listening on port 3000');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

Putting it all together

const Joi = require('joi');
const express = require('express');
const app = express();

const courses = {
    {
        id: 1
        , name: 'course1'
    }, {
        id: 2
        , name: 'course2'
    }, {
        id: 3
        , name: 'course3'
    }, 
}

app.post('/api/courses', (req, res) => {
    // add our reusable function to post()
    const {
        error
    } = validateCourse(req.body);
    if (error) {
        res.status(400).send(error.details[0].message);
        return;
    }
    const course = {
        id: course.length + 1
        , name: req.body.name
    };
    courses.push(course);
    res.send(course);
});
app.put('/api/courses/:id', (req, res) => {
            app.get('/api/courses/:id', (req, res) => {
                courses.find(c => c.id === parseInt(req.params.id));
                if (!course) return  res.status(404).send('The course with the given id was not found');
                // Object Destructuring
                const {
                    error
                } = validateCourse(req.body); // result.error
                if (error) {
                  return  res.status(400).send(error.details[0].message);
                }
                course.name = req.body.name;
                res.send(course);
            });
            //Reusable Validation function
            function validateCourse(course) {
                const schema = {
                    name: Joi.string().min(3).required()
                };
                //Return result
                return Joi.validate(course, schema);
            }
            app.listen(3000, () => console.log('listening on port 3000');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

delete()

  • delete is not much different from what we've been dealing with so far

delete() flow

  • look up course
  • if the course doesnt exist - return 404
  • Delete
  • Return the same course

app.delete('/api/courses/:id', (req, res) => {
// look up course

const course = courses.find(c => c.id === parseInt(req.params.id));

// 404

if (!course) return  res.status(404).send('The course with the given id was not found');


// Delete

const index = courses.indexOf(course);
// splice from array of courses
courses.splice(index, 1);

res.send(courses);
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (!course) return  res.status(404).send('The course with the given id was not found');
1

is the same as

if (!course) { 
                res.status(404).send('The course with the given id was not found');
              return;
             }
1
2
3
4
Last Updated: 8/11/2019, 3:34:12 AM