Overview
...
Table of Contents
Express js
//import express
const express = require('express');
// define app
const app = express();
2
3
4
express basics
express ships with usefull http functions
- .get()
- .post()
- .put()
- .delete()
| Function | Use | Example |
|---|---|---|
| .get() | ||
| .post() | ||
| .put() | ||
| .delete() |
get()
Arguments
- req
- Express 4.x - API Referencehttps://expressjs.com/en/api.html
- gives you access to properties of url
- types of properties you can get
- res
- response
Example
- import express
- define app
- define routes/path
- 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');
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');
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');
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');
2
3
4
5
6
7
8
9
10
11
12
output
// example url
// leadsapi.app/api/posts/2099/1
{
year: "2099",
month: "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');
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"
}
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}...`));
2
3
4
now if the port changes a new port is logged
post()
Arguments
req
- Express 4.x - API Referencehttps://expressjs.com/en/api.html
- gives you access to properties of url
- types of properties you can get
res
- response
similar to get() expects -- path -- request -- result
// 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);
});
2
3
4
5
6
7
8
9
10
11
12
13
Example json body to send to endpoint
{
"id" : 4,
"name" : "newcourse"
}
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
- Define validation pattern
- 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);
});
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
- Install joi
- Import or Require the joi package
install joi
npm i joi
const express = require('express');
const app = express();
//require joi
const Joi = require('joi');
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
- 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
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');
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');
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
req
- Express 4.x - API Referencehttps://expressjs.com/en/api.html
- gives you access to properties of url
- types of properties you can get
res
- response
similar to get() expects -- path -- request -- result
put() structure
app.put('/api/courses/:id', (req, res) => {
....
});
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');
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');
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
- more information
- Destructuring assignment - JavaScript | MDN
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}
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');
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');
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);
})
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');
is the same as
if (!course) {
res.status(404).send('The course with the given id was not found');
return;
}
2
3
4