Express.js

Express.js is a backend web application framework for building RESTful APIs with Node.js.

app.get('/', (req, res) => {
  res.send('🚀 Server is up and running!');
})

Express.js Basics

Creating a New Express.js Application

mkdir express

cd express

touch server.js

npm init -y

npm install express
const express = require('express');

const app = express();
const PORT = 3000;

// Logging Request Details

app.use((req, res, next) => {
  const timestamp = new Date().toDateString();
  console.log(`[${timestamp}] ${req.method} ${req.url} ${req.ip}`);
  next();
});

// Start the Server

app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}...`);
})

// End Points - /

app.get('/', (req, res) => {
  res.send('🚀 Server is up and running!');
})
node server.js

Adding JSON Middleware

For a typical REST or JSON API, this middleware is considered standard boilerplate. Without it, req.body will be undefined, your API can't read client data, and your POST/PUT routes will break.

// Automatically Parse JSON

app.use(express.json());

Enabling CORS

npm install cors
const cors = require('cors');

// Enable CORS

app.use(cors());

app.use(cors({
  origin: [
    'http://localhost:5173',
    'http://192.168.0.105:5173'
  ]
}));
res.setHeader('Access-Control-Allow-Origin', 'null');                   // Allow only local files (file://)
res.setHeader('Access-Control-Allow-Origin', '*');                      // Allow any origin
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8080');  // Allow local servers
res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');      // Allow production app origin

Listening on Specific Ports

app.listen(PORT, '127.0.0.1');  // just me
app.listen(PORT, '0.0.0.0');    // anyone who can reach me

End Points & Routes

Adding Route Params

Route params, also known as URL params, are dynamic, named URL segments used to capture specific values at defined positions in a URL. They enable apps to display dynamic content based on unique identifiers.

Example: /users/123

app.get("/users/:id", (req, res) => {
  console.log(req.params.id);
});

Adding Query Params

Query params are a defined set of key-value pairs attached to the end of a URL to pass additional, often optional, data to a server.

Example: https://example.com/products?category=shoes&color=blue

app.get("/search", (req, res) => {
  console.log(req.query.q);
});

Adding Body Params

Body params are inputs sent in the request body of an HTTP message, typically used to send large or unstructured data (often in JSON format) to a server. They are primarily used with POST, PUT, and PATCH requests for creating or updating resources.

{
  "email": "test@example.com",
  "password": "123456"
}
app.post("/users", (req, res) => {
  console.log(req.body.email);
});

Seeing Header Information

Headers are metadata sent from a client to a server within an HTTP request, providing context about the request, such as authentication, content types, and client capabilities. They act as key-value pairs that determine how the server should process the request and format the response.

app.post("/users", (req, res) => {
  console.log(req.headers);
});

Cookies?

GET Routes

app.get('/api/v1/user_data', (req, res) => {
  res.send('GET route...');
})

POST Routes

app.post('/api/v1/people', (req, res) => {
  res.send('POST route...');
});

Async Routes

app.get('/api/v1/weather_data', async (req, res) => {
  res.send('Async GET route...');
})
app.post('/api/v1/users', async (req, res) => {
  res.send('Async POST route...');
})

Adding Error Handling

app.get('/route', (req, res) => {
  try {
    // First try this...
    res.status(200).json({ success: "Success!" });
  } catch (error) {
    // If that fails, catch any errors...
    console.log(error);
    res.status(500).json({ error: error.message });
  }
});

Express.js + PostgreSQL

  • Client - A single, dedicated database connection to PostgreSQL.
  • Pool - Manages a set of PostgreSQL connections and hands them out as needed.

Installing PostgreSQL

npm install pg

Creating a Client Connection

With a Client connection, you are responsible for the connection lifecycle.

Create the Client

const { Client } = require('pg');

Specify the Connection Parameters

const client = new Client();

// or

const client = new Client ({
  host: 'localhost',
  user: 'grantdavis303',
  port: 5432,
  password: '',
  database: 'express_postgress'
})

Connect to the Client

client.connect();

Create a Query

client.query(`SELECT * FROM users;`, (err, res) => {
  if (!err) {
    console.log(res.rows);
  } else {
    console.log(err.message);
  }

  client.end();
})

Creating a Pool Connection

Pool connections usually don't require a manual connection.

Create the Pool

const { Pool } = require('pg');

Specify the Connection Parameters

const pool = new Pool();

// or

const pool = new Pool({
  host: 'localhost',
  user: 'grantdavis303',
  port: 5432,
  password: '',
  database: 'express_postgres_example'
});

Create a Query

const query = ""

const result = await pool.query(query);

// or

const result = await pool.query(
  "SELECT * FROM users WHERE id = $1",
  [userId]
);

Creating a GET End Point Returning JSON

// Get all sticky notes
app.get('/api/v1/notes', async (req, res) => {
  try {
    const query = await pool.query(
      `
        SELECT sticky_notes.*
        FROM users
        JOIN sticky_notes ON sticky_notes.user_id = users.id
        WHERE users.id = 1;
      `
    );

    res.send(query.rows);
  } catch (err) {
    res.status(500).send(err.message);
  }
})

Creating a POST End Point Returning JSON

// Create a sticky note
app.post('/api/v1/notes/create', async (req, res) => {
  console.log('Creating a new note request body:', req.body);

  try {
    const query = await pool.query(
      `
        INSERT INTO sticky_notes (user_id, title, content, color)
        VALUES (1, $1, $2, $3)
        RETURNING id, user_id, title, content, color, created_at, updated_at;
      `, [req.body.title, req.body.content, req.body.color]
    );

    res.send(query);
  } catch (err) {
    res.status(500).send(err.message);
  }
})

Creating a PATCH End Point Returning JSON

// Update a Note
app.patch('/api/v1/notes/update/:id', async (req, res) => {
  console.log('Updating a note request body:', req.body);

  try {
    const query = await pool.query(
      `
        UPDATE sticky_notes
        SET title = $1, content = $2, color = $3, updated_at = now()
        WHERE sticky_notes.id = ${req.params.id}
        RETURNING id, user_id, title, content, color, created_at, updated_at;
      `, [req.body.title, req.body.content, req.body.color]
    );

    res.send(query);
  } catch (err) {
    res.status(500).send(err.message);
  }
})

Creating a DELETE End Point Returning JSON

// Delete a sticky note
app.delete('/api/v1/notes/delete/:id', async (req, res) => {
  try {
    const query = await pool.query(
      `
        DELETE FROM sticky_notes
        WHERE id = ${req.params.id};
      `
    );

    res.send(query);
  } catch (err) {
    res.status(500).send(err.message);
  }
})