A few months ago, I had to build a full-stack app having only front-end experience. This is an overview of backend tools and practices if you discover yourself in the same situation.
I’m a front-end dev, but I need to write a full-stack app. What to do?#
To write an app, you’ll have to figure out three things: a programming language for the backend, a database to store data, and infrastructure.
Popular backend languages include Ruby, Python, JavaScript (Node.js), Java and PHP. If you’re a front-end developer, start with Node.js – things would be way easier.
I’ll cover databases and infrastructure in the next parts.
OK, I’ve just installed Node.js. What is it?#
Node.js enables you to write servers in JavaScript. It’s a JavaScript engine similar to the one Google Chrome has. You pass it a JS file, and the engine runs it:
// index.js
console.log('42');
# Shell
$ node ./index.js
42
To launch a real server, you’ll need to call a specific method provided by Node.js (e.g, createServer()
from the net
module). In practice, a few people do this – almost everyone uses high-level wrappers.
I’ve heard a bit about Node.js and callbacks. What’s the deal with them?#
The primary thing you need to know about Node.js is that it performs all long actions (like reading data from a database) asynchronously – using callbacks. This means that to make a DB request, you don’t do this:
const data = sql.query('SELECT name from users');
console.log(data);
but do this instead:
sql.query('SELECT name from users', (error, data) => {
console.log(data);
});
Under the hood, Node.js sends a request to the database and immediately continues executing the code. And when the request completes, Node.js calls the callback and passes the data to it. This helps to write servers that handle lots of requests at the same time.
Can I write code without callbacks?#
Yes. If you don’t like passing functions into other functions, there’re a couple of alternatives:
-
Option A: use promises instead of callbacks. You’ll still have to pass functions around – but you’ll write code without excessive nesting which often happens with callbacks:
Useutil.promisify
to adapt native callback-based APIs to promises// With callbacks const fs = require('fs'); fs.readFile('./text.txt', (err, data) => { console.log(data); }); // With promises const fs = require('fs'); const util = require('util'); const readFile = util.promisify(fs.readFile); readFile('./text.txt') .then((data) => { console.log(data); }) .catch((err) => { // ... });
-
Option B: use
async/await
with promise APIs. This lets you write code that looks synchronous:async/await
is available with Node.js 7.6+.// With callbacks const fs = require('fs'); fs.readFile('./text.txt', (err, data) => { console.log(data); }); // With async/await const fs = require('fs'); const util = require('util'); const readFile = util.promisify(fs.readFile); try { const data = await readFile('./text.txt') console.log(data); } catch (err) { // ... };
Many native Node.js APIs (like fs.readFile
) also have synchronous alternatives (like fs.readFileSync
):
const fs = require('fs');
const data = fs.readFileSync('./text.txt');
However, avoid using them on real servers – or the server would die just from a few simultaneous clients. (These APIs are useful in console scripts though.)
OK, how do I create a server?#
Use Express:
const express = require('express');
const app = express();
// ↓ Define a REST request
app.get('/api/user', (req, res) => {
res.json({ name: 'Jason Bourne' });
});
// ↓ Ask the server to return statis files
// from the `public` dir (if the /api/user route didn’t match)
app.use('*', express.static('public'));
app.listen(3000, () => {
console.log('Server is listening on port 3000')
});
Express is useful for building static servers and REST APIs.
How do I build something real?#
In a real app, apart from an HTTP server, you’ll need to implement a few more things. Here’re the common solutions for them:
- Authorization: Passport.js. Passport.js works with login-password pairs, social networks, OAuth and lots of other login strategies. A SaaS solution like Auth0 is also an option
- Validating requests: express-validator. express-validator normalizes and validates REST data you recieve from the clients
- Sending emails: Nodemailer. Nodemailer works with SMTP; it also has simplified settings for AWS Simple Email Service
- Password hashing: bcrypt. Important: learn how to store and hash passwords properly
- Logging: loglevel, debug, or winston. I haven’t found a perfectly satisfying solution
- Running the app in production: PM2. PM2 restarts an app in case of crashes, deploys a new version without a downtime, distributes the load to multiple instances and allows to deploy remotely
That’s it! The next part of the guide will introduce you into databases (specifically, into MySQL).