Search code examples
node.jsexpressheroku

Why is my Heroku Express API data persistent even though it's only coming from a variable


I created a simple API using express, and deployed it to Heroku, this is the code for it:

const express = require("express");
const app = express();
const cors = require("cors");
app.use(express.json());
app.use(cors());
app.use(express.static("build"));
let notes = [
    {
        id: 1,
        content: "HTML is easy",
        date: "2022-05-30T17:30:31.098Z",
        important: true,
    },
    {
        id: 2,
        content: "Browser can execute only Javascript",
        date: "2022-05-30T18:39:34.091Z",
        important: false,
    },
    {
        id: 3,
        content: "GET and POST are the most important methods of HTTP protocol",
        date: "2022-05-30T19:20:14.298Z",
        important: true,
    },
];

const generateId = (arr) => {
    const maxId = arr.length < 0 ? 0 : Math.max(...arr.map((item) => item.id));
    return maxId + 1;
};

app.get("/", (req, res) => {
    res.send(`<h1>Hello World!</h1>`);
});

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

app.get("/api/notes/:id", (req, res) => {
    const id = Number(req.params.id);
    const note = notes.find((note) => note.id === id);
    if (note) {
        res.json(note);
    } else {
        res.status(404).end();
    }
});

app.delete("/api/notes/:id", (req, res) => {
    const { id } = Number(req.params);
    notes = notes.filter((note) => note.id !== id);
    res.status(204).end();
});

app.post("/api/notes", (req, res) => {
    const body = req.body;
    if (!body.content) {
        return res.status(400).json({
            error: "Content Missing",
        });
    }
    const note = {
        content: body.content,
        important: body.important || false,
        date: new Date(),
        id: generateId(notes),
    };
    notes = notes.concat(note);
    res.json(note);
});

app.put("/api/notes/:id", (req, res) => {
    const newNote = req.body;
    notes = notes.map((note) => (note.id !== newNote.id ? note : newNote));
    res.json(newNote);
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

as you can see, the data served to the frontend (A React app) comes from the '/api/notes' endpoint, this endpoint returns a response with the notes array.
After deploying to Heroku (https://fierce-chamber-07494.herokuapp.com/) the functionality of adding notes, and setting importance all work perfectly normal, but what I wasn't expecting was for the data to be persistent even after refreshing the page, visiting it in another device, etc. The data only comes from a variable, not a database, nothing. So why is it persistent? does Heroku modify the variable itself? how does this work?


Solution

  • The top-level code of an Express server often runs once, when you start up the server. Variables declared at that top level are then persistent if there are any handlers that reference them.

    Consider how a client-side page with JavaScript works - the page loads, and then the JavaScript runs. If you keep the tab open for a couple hours and then come back to it, you'll see that variables declared on pageload still exist when you come back. The same sort of thing is happening here, except that the persistent environment is on your server, rather than on a client's page.

    The code that starts up the Express server - that is, your

    const express = require("express");
    const app = express();
    const cors = require("cors");
    app.use(express.json());
    app.use(cors());
    ...
    

    and everything below it - doesn't run every time a request is made to the server. Rather, it runs once, when the server starts up, and then when requests are made, request handlers get called - such as the callback inside

    app.get("/", (req, res) => {
        res.send(`<h1>Hello World!</h1>`);
    });
    

    So, the variables declared at the top-level are persistent (even across different requests) because that server environment is persistent.

    That said - something to keep in mind with Heroku is that with their free and cheap tiers, if no request is made to your app for a period of time (maybe 30 minutes), Heroku will essentially turn your server off by spinning down the dyno until another request is made, at which point they'll start your server up again, which will run the top-level code again. So while you'll sometimes see a top-level variable that appears to have its mutated values persist over multiple requests, that's not something to count on if your Heroku plan doesn't guarantee 100% uptime for your server.