Some useful ES6 features you should know in learning React and react-router etc.

I am writing this post for beginners for those who do not know yet.

When you are trying to learn React and related libraries or components such as React Router, then you need to know es6 language features. Most modern React tutorials use them extensively, but no one will explain about the language features.

For example, one of the language features used in 8309476661 is object destructuring.

From Mozilla site, it is said that the destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

Let’s see the example from 5089054328

1
2
3
4
5
var o = {p: 42, q: true};
var {p, q} = o;

console.log(p); / 42
console.log(q); / true

Object properties are unpacked to separate variables.

Now, let’s see react-router basic example from 8476490638.

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

const BasicExample = () => (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>

<hr />

<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/topics" component={Topics} />
</div>
</Router>
);

const Home = () => (
<div>
<h2>Home</h2>
</div>
);

const About = () => (
<div>
<h2>About</h2>
</div>
);

const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>

<Route path={`${match.url}/:topicId`} component={Topic} />
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);

const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
);

export default BasicExample;

See the component declaration Topics and Topic

1
const Topics = ({ match }) => (
1
const Topic = ({ match }) => (

They are using object destructuring.
How?

From the following code

1
<Route path="/topics" component={Topics} />

The component inside Route will be passed a route property object including properties match, location and history.

So, for example, we can rewrite the Topic component as following:

1
2
3
4
5
const Topic = (props) => (
<div>
<h3>{props.match.params.topicId}</h3>
</div>
);

This is the same result.
But, we are interested only in one property which is match.
So, object destructuring assignment was used.

This is similary to the following code.

1
2
3
const props = { match: 'some value', location: 'some value', history: 'some value'};
const { match } = props;
console.log(match);

Another feature is spread syntax.

Again, copied [Mozilla site].(/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)

Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

JavaScript Demo: Expressions - Spread syntax

1
2
3
4
5
6
7
8
function sum(x, y, z) {
return x + y + z;
}

const numbers = [1, 2, 3];

console.log(sum(...numbers));
/ expected output: 6

From React router tutorial

1
2
3
4
5
6
7
8
9
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)

<FadingRoute path="/cool" component={Something}/>

In this case, we could have rewritten like the following:

1
2
3
4
5
6
7
8
9
const FadingRoute = ({ component: Component, path }) => (
<Route path={path} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)

<FadingRoute path="/cool" component={Something}/>

FadingRoute component wants to pass all the properties to Route component except component.
You can see that the above code is not convenient when there are a lot of properties to pass.
For example, we will be writing like the following:

1
2
3
4
5
6
7
8
9
const FadingRoute = ({ component: Component, path, prop1, prop2, prop3 }) => (
<Route path={path} prop1={prop1} prop2={prop2} prop3={prop3} render={props => (
<FadeIn>
<Component {...props}/>
</FadeIn>
)}/>
)

<FadingRoute path="/cool" prop1="value" prop2="value2" prop3="value3" component={Something}/>

So, spread syntax was used for this kind of situation.

Using sequelize cli for database migrations

Express an express with express generator command

1
2
3
npm install express-generator -g
express sample-app --view=ejs
cd sample-app && npm install

*** Note that using express is optional to use sequelize-cli. But, to be simple for beginners,
it is used for the base app for this tutorial.

Create a folder named api and create another folder named db inside api.

Install sequelize and other required packages for database.

1
2
3
4
5
# Using NPM
npm install sequelize

# The following for postgres databse, for other database see sequelize official site
$ npm install pg pg-hstore

Install sequelize cli

1
npm install -g sequelize-cli

Create a file named .sequelizerc to customize migrations and config paths etc by running the following command.

1
touch .sequelizerc

Enter the following content in .sequelizerc file

1
2
3
4
5
6
7
8
const path = require('path');

module.exports = {
'config': path.resolve('config', 'database.json'),
'models-path': path.resolve('api', 'db/models'),
'seeders-path': path.resolve('api', 'db/seeders'),
'migrations-path': path.resolve('api', 'db/migrations')
}

Run sequelize init command

1
sequelize init

Create a new database in postgres (for example mydb) and update the connection info in config/database.json file as in the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"development": {
"username": "postgres",
"password": "postgres",
"database": "mydb",
"host": "127.0.0.1",
"dialect": "postgres"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}

Create your first migration script to create a table by running the following command

1
sequelize migration:generate --name create-user

This will create a migration file named such as XXXXXXXXXXXXXX-create-user.js in api/db/migrations folder.
Replace that file with the following content

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
'use strict';

module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
username: {
type: Sequelize.STRING,
allowNull: false
},
created_at: {
allowNull: false,
type: Sequelize.DATE
},
updated_at: {
allowNull: false,
type: Sequelize.DATE
}
});
},

down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('users');
}
};

Now you can start your migration script for the first time by running the following command.

1
sequelize db:migrate

To initialize some demo datas you can use sequelize seeding feature. Run the following command.

1
sequelize seed:generate --name demo-users

This will create a file XXXXXXXXXXXXXX-demo-users.js and replace with the following contents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict';

module.exports = {
up: (queryInterface, Sequelize) => {
const users = [];
for (let i = 1; i <= 10; i++) {
const date = new Date();
users.push({ username: `user ${i}`, created_at: date, updated_at: date});
}
return queryInterface.bulkInsert('users', users, {});
},

down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('users', null, {});
}
};

Run the following command to run all the seeds created.

1
sequelize db:seed:all

*** Note that sequelize cli does not generate model definition files. You have to create them by yourself.

Now, you know how to use sequelize cli for database migrations and seeding.
To create another table (for example, posts), create a new migration by running the following command.

1
sequelize migration:generate --name create-post

And enter the following content and run sequelize db:migrate again.

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
'use strict';

module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('posts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
content: {
type: Sequelize.TEXT
},
user_id: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
},
created_at: {
allowNull: false,
type: Sequelize.DATE
},
updated_at: {
allowNull: false,
type: Sequelize.DATE
}
});
},

down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('posts');
}
};

To use sequelize models from other parts of the application create the following model files in api/db/models

api/db/models/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict';

module.exports = function (sequelize, DataTypes) {
const User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
}
}, {
underscored: true,
tableName: 'users'
});

return User;
};

api/db/models/post.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict';

module.exports = function (sequelize, DataTypes) {
const Post = sequelize.define('Post', {
content: {
type: DataTypes.TEXT,
allowNull: false,
}
}, {
underscored: true,
tableName: 'posts'
});

Post.associate = function (models) {
Post.belongsTo(models.User, {
foreignKey: 'user_id',
});
};

return Post;
};

Now you are ready to use sequelize models.

Making authenticated request with supertest

Testing authenticated request with supertest in express application

Sample code

app.js

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
57
58
59
60
61
62
63
64
65
66
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const logger = require('morgan');

const passport = require('./passportConfig');

const app = express();

/ view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({ secret: 'your secret here', resave: false, saveUninitialized: false }));
app.use(express.static(path.join(__dirname, 'public')));

/ Initialize Passport and restore authentication state, if any, from the
/ session.
app.use(passport.initialize());
app.use(passport.session());

app.post('/api/login', passport.authenticate('local'),
(req, res) => {
res.json({});
});

app.get('/api/logout',
(req, res) => {
req.logout();
res.json({});
});

const ensureLoggedIn = (req, res, next) => {
if (req.user) return next();
res.status(401).json({});
}

app.get('/api/me',
ensureLoggedIn,
(req, res) => {
res.json(req.user);
});

/ catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});

/ error handler
app.use(function(err, req, res, next) {
/ set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

/ render the error page
res.status(err.status || 500);
res.render('error');
});

module.exports = app;


passportConfig.js

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
57
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

const users = [
{id: 1, email: 'user1@email.com', password: 'password1'},
{id: 1, email: 'user2@email.com', password: 'password2'}
]

const findUserByEmail = (email) => {
const user = users.find((u) => {
return u.email === email;
});
return user;
};

const findUserById = (id) => {
const user = users.find((u) => {
return u.id === id;
});
return user;
};

passport.use(new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
(username, password, done) => {
try {
const user = findUserByEmail(username);
if (!user) {
return done(null, false, { message: 'Incorrect email or password' });
}
if (user.password !== password) {
return done(null, false, { message: 'Incorrect email or password' });
}
return done(null, user);
} catch (err) {
return done(err);
}
}),
);

passport.serializeUser((user, cb) => {
cb(null, user.id);
});

passport.deserializeUser(async (id, cb) => {
try {
const user = findUserById(id);
cb(null, user);
} catch (err) {
cb(err);
}
});

module.exports = passport;


test.js

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
57
58
59
60
61
62
const app = require('./app');
const request = require('supertest');

const agent = request.agent(app);

describe('API Testing', function () {
it('respond with with 401 status code', (done) => {
request(app)
.get('/api/me')
.expect(401).then((res) => {
done();
}).catch(error => {
done(error);
});
});

it('should login user', function (done) {
agent
.post('/api/login')
.send({ email: 'user1@email.com', password: 'password1' })
.expect(200)
.then((err, res) => {
done();
}).catch((err) => {
done(err);
});
});

it('should allow access', function (done) {
agent
.get('/api/me')
.expect(200)
.then(function (err, res) {
done();
}).catch((err) => {
done(err);
});
});

it('should allow logout user', function (done) {
agent
.get('/api/logout')
.expect(200)
.then(function (err, res) {
done();
}).catch((err) => {
done(err);
});
});

it('should not allow access now', function (done) {
agent
.get('/api/me')
.expect(401)
.then(function (err, res) {
done();
}).catch((err) => {
done(err);
});
});

});

Install mocha and run test.js

1
2
$ npm install --global mocha
$ mocha test.js

saw-shaped