Build a simple chat web app using Faye, Express and Vue - Part 1
I have been keeping my eyes on Vuejs for quite some time, and I finally decided to build something with it. I am going to build a simple chat web app using Faye, Express and Vue. I have been using Faye and Express for several years, Vue is the new guy here.
The structure of the app is straightforward, there are 3 main components communicating with each other to serve the requests.
- The API server to handle data persistence, authentication and authorization (through Github or other OAuth services).
- The pub/sub messaging server to push messages to the client side in real-time (through websocket, evensource or long-polling).
- The static file server to show the actual user interface.
In the first part, I am going to start with setting up the API server and a simple data persistence layer:
API server⌗
I love to work with Express because it only takes several minutes to get the server up and running. It provides a thin layer on top of the native http module in Nodejs but still allows a wide range of customization to be done easily.
I will go with REST because I don’t see any point in doing something fancy like GraphQL for this trival app. At its simplest form, the API server looks like this
// app.js
const express = require('express'),
bodyParser = require('body-parser'),
cookieParser = require('cookie-parser'),
config = require('./config');
const allowCors = (req, res, next) => {
res.header('Access-Control-Allow-Origin', config.get('web.url'));
res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', true);
next();
};
const app = express();
app.use(allowCors);
app.disable('x-powered-by');
app.enable('trust proxy');
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/messages', require('./routes/messages'));
It’s pretty straightforward, there are 2 things worth mentioning, the config
and the use of router
Config module⌗
I am using nconf
to build a hierarchy of configuration for my projects. The idea is simple, first it loads a global configuration file (config.yml
in my case). Then it tries to load the environment specific configuration file, it’s can be development
, production
, staging
, testing
or ci
(for continuous integration setup).
For security purposes, I don’t store any passwords, API keys in git repository (and you should too). Instead, I set them in environment variables during the deployment process and nconf
automatically picks them up and overwrites the existing configuration.
For example, when I set the database connection variable to the environment variable called psql__host
, I can get it by calling config.get('psql.host')
.
// config/index.js
const _ = require('lodash'),
yaml = require('js-yaml');
let nconf = require('nconf');
const env = process.env.NODE_ENV || 'development';
module.exports = nconf.env({
separator: '__',
lowerCase: true,
logicalSeparator: '.'
}).file('env-configs', {
file: `${__dirname}/config.${env}.yml`,
format: {
parse: yaml.safeLoad,
stringify: yaml.safeDump
},
logicalSeparator: '.'
}).file('default-configs', {
file: `${__dirname}/config.yml`,
format: {
parse: yaml.safeLoad,
stringify: yaml.safeDump
},
logicalSeparator: '.'
});
Router⌗
The concept of router in Express is simple, you divide your application into smaller independent “routers” and compose them in the main Express app instance. I usually put the files in sub folders corresponding to their path. For example:
GET /api/1/messages
->/project/routes/api/1/messages/list.js
GET /api/1/messages/1
->/project/routes/api/1/messages/read.js
POST /api/1/messages
->/project/routes/api/1/messages/create.js
PUT /api/1/messages
->/project/routes/api/1/messages/update.js
DELETE /api/1/messages/1
->/project/routes/api/1/messages/delete.js
Then with a simple recursive function I can loop through all the files and construct the end points accordingly. It can also takes care of API versoning and other middleware setup.
However, in this small project, I just go with a simple router file containing 4 end points for interacting with messages
resource. There is no end point to get one message because I am not going to support that in the UI.
// routes/messages.js
const express = require('express'),
router = express.Router();
router.get('/', (req, res) => {
// list
});
router.post('/', (req, res) => {
// create
});
router.put('/:id', (req, res) => {
// update
});
router.delete('/:id', (req, res) => {
// delete
});
module.exports = router;
When composing it with the main app
app.use('/messages', require('./messages'));
I have the following routes
GET /messages
POST /messages
PUT /messages/1
DELETE /messages/1
Data persistence⌗
For the purposes of this simple app, I don’t need anything fancier than sqlite3 and a bunch of functions, sql queries to store and retrieve data.
// sqlite.js
const sqlite = require('sqlite'),
Promise = require('bluebird');
module.exports = {
getMessages(limit = 50) {},
createMessage(content) {},
deleteMessage(id) {},
updateMessage(id, content) {},
// must call this before everything else
up() {
return sqlite.open(`${__dirname}/chat.sqlite`, {Promise}).then(() => {
return sqlite.migrate({
migrationsPath: `${__dirname}/migrations`
});
});
}
};
I skip all the SQL queries on purpose to present a clearer view of the module. Notice that there is an up
function that must be called before starting the application to run migration files.
// start.js
const app = require('./src/app'),
service = require('./src/service'),
config = require('./src/config');
const port = config.get('express.port');
const host = config.get('express.host');
service.up().then(() => {
app.listen(port, host, () => {
console.log(`API server is running at ${host}:${port}`);
});
}).catch((e) => {
console.error('Failed to start application', e);
});
And the migration
-- Up
CREATE TABLE messages (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
-- Down
DROP TABLE messages;
With this I have the first part of the application ready. The source code can be found here https://github.com/tanqhnguyen/simple-chat-api.
In the next part, I am going to write about the authentication and authorization process using Passport.