Phase 0: Backend Setup

← Back to Main Tutorial

Understanding the Problem

In this phase, we need to set up the backend of our authentication application. This includes creating the project structure, installing dependencies, setting up Sequelize, initializing Express, and configuring security middlewares.

The goal is to have a well-structured Express application ready for implementing authentication features in later phases.

Planning the Solution

  1. Create the project folder structure
  2. Set up the README.md file
  3. Create a .gitignore file
  4. Initialize Git repository
  5. Create backend and frontend folders
  6. Install necessary backend dependencies
  7. Set up environment variables
  8. Configure Sequelize
  9. Set up Express application with security middleware
  10. Create routes
  11. Configure the server
  12. Test the server

Implementing the Solution

1. Create Project Folder Structure

Start by creating a root folder for your project. The name should be meaningful and represent your project.

mkdir my_authentication_project
cd my_authentication_project

2. Set up README.md

Create a README.md file at the root of your project to document your API and database schema.

touch README.md

Add the following content to README.md:

# My Authentication Project

## Database Schema Design

![db-schema](./images/db-schema.png)

## API Documentation

### All endpoints that require authentication

All endpoints that require a current user to be logged in.

* Request: endpoints that require authentication
* Error Response: Require authentication
  * Status Code: 401
  * Headers:
    * Content-Type: application/json
  * Body:

    ```json
    {
      "message": "Authentication required"
    }
    ```

3. Create .gitignore File

Create a .gitignore file at the root to exclude certain files from version control:

touch .gitignore

Add the following content to .gitignore:

node_modules
.env
build
.DS_Store
*.db

4. Initialize Git Repository

Configure Git to use 'main' as the default branch, initialize the repository, and make your first commit:

git config --global init.defaultBranch main
git init
git add .
git commit -m 'Initial commit'

Create a remote GitHub repository and connect it to your local repository:

git remote add origin <github-remote-url>
git push origin main

5. Create Backend and Frontend Folders

Create folders to separate backend and frontend code:

mkdir backend frontend images

6. Install Backend Dependencies

Initialize the package.json file in the backend folder and install necessary dependencies:

cd backend
npm init -y
npm install cookie-parser cors csurf dotenv express express-async-errors helmet jsonwebtoken morgan per-env sequelize@6 sequelize-cli@6 pg
npm install -D sqlite3 dotenv-cli nodemon

7. Set Up Environment Variables

Create a .env file in the backend folder:

touch .env

Add the following content to the .env file:

PORT=8000
DB_FILE=db/dev.db
JWT_SECRET=your_secret_here
JWT_EXPIRES_IN=604800
SCHEMA=your_schema_name_here

To generate a strong JWT secret, you can use the openssl command:

openssl rand -base64 10

8. Create Configuration Files

Create a config folder in the backend folder and add an index.js file for environment variables:

mkdir -p backend/config
touch backend/config/index.js

Add the following content to config/index.js:

// backend/config/index.js
module.exports = {
  environment: process.env.NODE_ENV || 'development',
  port: process.env.PORT || 8000,
  dbFile: process.env.DB_FILE,
  jwtConfig: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN
  }
};

9. Set Up Sequelize

Create a .sequelizerc file in the backend folder:

touch backend/.sequelizerc

Add the following content to .sequelizerc:

// backend/.sequelizerc
const path = require('path');

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

Initialize Sequelize:

npx sequelize init

Update the config/database.js file with the following content:

// backend/config/database.js
const config = require('./index');

module.exports = {
  development: {
    storage: config.dbFile,
    dialect: "sqlite",
    seederStorage: "sequelize",
    logQueryParameters: true,
    typeValidation: true
  },
  production: {
    use_env_variable: 'DATABASE_URL',
    dialect: 'postgres',
    seederStorage: 'sequelize',
    dialectOptions: {
      ssl: {
        require: true,
        rejectUnauthorized: false
      }
    },
    define: {
      schema: process.env.SCHEMA
    }
  }
};

Create a psql-setup-script.js file in the backend folder:

touch backend/psql-setup-script.js

Add the following content to psql-setup-script.js:

// backend/psql-setup-script.js
const { sequelize } = require('./db/models');

sequelize.showAllSchemas({ logging: false }).then(async (data) => {
  if (!data.includes(process.env.SCHEMA)) {
    await sequelize.createSchema(process.env.SCHEMA);
  }
});

Run a test migration to ensure Sequelize is set up correctly:

npx dotenv sequelize db:migrate

10. Set Up Express Application

Create an app.js file in the backend folder:

touch backend/app.js

Add the following content to app.js:

// backend/app.js
const express = require('express');
require('express-async-errors');
const morgan = require('morgan');
const cors = require('cors');
const csurf = require('csurf');
const helmet = require('helmet');
const cookieParser = require('cookie-parser');

const { environment } = require('./config');
const isProduction = environment === 'production';

const app = express();

// Connect morgan middleware for logging
app.use(morgan('dev'));

// Parse cookies and JSON bodies
app.use(cookieParser());
app.use(express.json());

// Security middleware
if (!isProduction) {
  // Enable CORS only in development
  app.use(cors());
}

// Helmet helps set security headers
app.use(
  helmet.crossOriginResourcePolicy({
    policy: "cross-origin"
  })
);

// Set the _csrf token and create req.csrfToken method
app.use(
  csurf({
    cookie: {
      secure: isProduction,
      sameSite: isProduction && "Lax",
      httpOnly: true
    }
  })
);

// Connect routes
const routes = require('./routes');
app.use(routes);

module.exports = app;

11. Create Routes

Set up the routes folder and create an index.js file:

mkdir -p backend/routes
touch backend/routes/index.js

Add the following content to routes/index.js:

// backend/routes/index.js
const express = require('express');
const router = express.Router();

router.get('/hello/world', function(req, res) {
  res.cookie('XSRF-TOKEN', req.csrfToken());
  res.send('Hello World!');
});

module.exports = router;

12. Set Up Server

Create a bin folder and www file to start the server:

mkdir -p backend/bin
touch backend/bin/www

Add the following content to bin/www:

#!/usr/bin/env node
// backend/bin/www

// Import environment variables
require('dotenv').config();

const { port } = require('../config');

const app = require('../app');
const db = require('../db/models');

// Check the database connection before starting the app
db.sequelize
  .authenticate()
  .then(() => {
    console.log('Database connection success! Sequelize is ready to use...');

    // Start listening for connections
    app.listen(port, () => console.log(`Listening on port ${port}...`));
  })
  .catch((err) => {
    console.log('Database connection failure.');
    console.error(err);
  });

13. Configure Package.json Scripts

Update your backend/package.json file to include the following scripts:

"scripts": {
  "sequelize": "sequelize",
  "sequelize-cli": "sequelize-cli",
  "start": "per-env",
  "start:development": "nodemon ./bin/www",
  "start:production": "node ./bin/www",
  "build": "node psql-setup-script.js"
}

14. Test the Server

Start the server and test the hello world route:

cd backend
npm start

Open your browser and navigate to http://localhost:8000/hello/world. You should see "Hello World!" displayed, and two cookies (_csrf and XSRF-TOKEN) should be set in your browser.

15. Add CSRF Token Restore Route

Update the routes/index.js file to include a route for restoring the CSRF token:

// backend/routes/index.js
// ...

// Add a XSRF-TOKEN cookie
router.get("/api/csrf/restore", (req, res) => {
  const csrfToken = req.csrfToken();
  res.cookie("XSRF-TOKEN", csrfToken);
  res.status(200).json({
    'XSRF-Token': csrfToken
  });
});

module.exports = router;

This route will be useful for frontend development to ensure the CSRF token is always available.

Review the Solution

At this point, you have successfully set up the backend of your authentication application. Let's review what you've accomplished:

Your application is now ready for implementing API routes, which we'll cover in Phase 1.

Common Issues and Solutions

Next Steps

Now that you have set up the backend of your application, you are ready to move on to Phase 1: API Routes, where you will create API endpoints for your application.