diff --git a/common-content/en/module/decomposition/adding-quotes/index.md b/common-content/en/module/decomposition/adding-quotes/index.md
new file mode 100644
index 000000000..13b904751
--- /dev/null
+++ b/common-content/en/module/decomposition/adding-quotes/index.md
@@ -0,0 +1,35 @@
++++
+title = "Adding quotes"
+headless = true
+time = 60
+facilitation = false
+emoji= "➕"
+hide_from_overview = true
+objectives = [
+ "POST data from a frontend to a backend in JSON format.",
+]
++++
+
+{{}}
+Add a form to your frontend which allows users to add quotes to the backend's list of quotes.
+
+Note: Your backend expects the quotes to be submitted as JSON. This means you will need to use a `fetch` request from JavaScript to do the POSTing.
+
+You can't just use a `}}
+
+After a user tries to add a quote, if they successfully added the quote we should give the user some feedback so they know it was successful.
+
+If a user tried to add a quote and the `fetch` failed (perhaps because the backend wasn't running), or the response said there was an error, we should give the user some feedback so they know something went wrong (and maybe what they should do about it).
+
+Now that our backend allows users to post quotes, we may want to restrict what can be posted.
+
+For instance, we probably don't want to let people post quotes which are empty.
+
+We may want to do some validation of the input our users give us.
+
+{{}}
+Where do you think we want to do this validation?
+
+On the frontend? On the backend? Or both?
+{{}}
diff --git a/common-content/en/module/decomposition/backend-limitations/index.md b/common-content/en/module/decomposition/backend-limitations/index.md
new file mode 100644
index 000000000..a3c61485a
--- /dev/null
+++ b/common-content/en/module/decomposition/backend-limitations/index.md
@@ -0,0 +1,57 @@
++++
+title = "Limitations of backends"
+headless = true
+time = 20
+facilitation = false
+emoji= "📖"
+objectives = [
+ "Explain why a backend on its own doesn't provide reliable data persistence.",
+ "Explain why we may prefer doing work in a frontend rather than backend to avoid latency.",
+]
++++
+
+We've already explored limitations of frontends.
+
+We know a backend is just a program that runs for a long time.
+
+### Lifetime
+
+A major limitation of backends is that "a long time" probably isn't forever.
+
+Sometimes we change the code of the backend. Or need restart the computer it's running on for an upgrade. Or its computer loses power and we need to start it again.
+
+When this happens, the program starts again.
+
+Think back to our quote server that allows users to POST new quotes.
+
+If we had to stop the server and start it again, we would lose all of the quotes users had saved.
+
+They're just stored in a variable, and a variable only lasts while the program it's in is running.
+
+### Location
+
+Another major limitation of a backend is where the code runs.
+
+A backend's code runs on whatever server it's running on.
+
+In contrast, a web frontend's code runs in the user's web browser.
+
+#### Latency
+
+One problem here is latency. Depending on where the backend and the user are physically located, it may take anywhere between 1ms and 500ms for a request to go between them.
+
+If every time you clicked on something on a web page you needed to talk to the backend, you may need to wait half a second just for the request to travel to the server and for the response to travel back, ignoring how long it takes to actually process the request. This would be unusably slow for many applications.
+
+#### Context
+
+Because web frontends run in the user's web browser, they have easy access to lots of information about the user's computer. For instance, they know what language it's configured in, what time zone it's configured in, how big the browser window is, etc.
+
+If our frontend code were instead running in a backend, the browser may need to include all of this information in every request it makes, just in case the backend needs to know it. This has a couple of drawbacks: It makes the requests bigger (which makes them slower, and maybe cost more), and it ends up sharing lots of data with the server that it may not need, which may compromise the user's privacy.
+
+### Pull not push
+
+A backend lives at a well-known address - we know how to connect to it. A user's web browser does not.
+
+This means that a backend cannot try to open a connection to a user's web browser. The web browser needs to initiate the request, and then the backend can reply to the request.
+
+Once a web browser opens a request to a backend, there are some ways to keep a bi-directional communication channel open. But the very first request needs to come from the web browser.
diff --git a/common-content/en/module/decomposition/backend-statefulness/index.md b/common-content/en/module/decomposition/backend-statefulness/index.md
new file mode 100644
index 000000000..2e02d59da
--- /dev/null
+++ b/common-content/en/module/decomposition/backend-statefulness/index.md
@@ -0,0 +1,89 @@
++++
+title = "Backend statefulness"
+headless = true
+time = 30
+facilitation = false
+emoji= "📖"
+objectives = [
+ "Identify whether some program is stateful or stateless.",
+]
++++
+
+Our example backend is **stateless**. If you make several requests to it, it will always do the same thing (even though doesn't always return exactly the same result!).
+
+If you made a request from a different computer, or from different country, or on a different day, it would keep doing exactly the same thing. If you restarted the server, it would keep doing exactly the same thing.
+
+If our backend allowed users to add new quotes, it would start having _state_. It would need to remember what quotes had been added. It would be **stateful**.
+
+```js
+import express from "express";
+
+const app = express();
+const port = 3000;
+
+const quotes = [
+ {
+ quote: "Either write something worth reading or do something worth writing.",
+ author: "Benjamin Franklin",
+ },
+ {
+ quote: "I should have been more kind.",
+ author: "Clive James",
+ },
+];
+
+function randomQuote() {
+ const index = Math.floor(Math.random() * quotes.length);
+ return quotes[index];
+}
+
+app.get("/", (req, res) => {
+ const quote = randomQuote();
+ res.send(`"${quote.quote}" -${quote.author}`);
+});
+
+app.post("/", (req, res) => {
+ const bodyBytes = [];
+ req.on("data", chunk => bodyBytes.push(...chunk));
+ req.on("end", () => {
+ const bodyString = String.fromCharCode(...bodyBytes);
+ let body;
+ try {
+ body = JSON.parse(bodyString);
+ } catch (error) {
+ console.error(`Failed to parse body ${bodyString} as JSON: ${error}`);
+ res.status(400).send("Expected body to be JSON.");
+ return;
+ }
+ if (typeof body != "object" || !("quote" in body) || !("author" in body)) {
+ console.error(`Failed to extract quote and author from post body: ${bodyString}`);
+ res.status(400).send("Expected body to be a JSON object containing keys quote and author.");
+ return;
+ }
+ quotes.push({
+ quote: body.quote,
+ author: body.author,
+ });
+ res.send("ok");
+ });
+});
+
+app.listen(port, () => {
+ console.error(`Quote server listening on port ${port}`);
+});
+```
+
+Here we have added a new request handler. If someone makes a POST request to the path `/`, we try to interpret the body they posted as a JSON object.
+
+If we can find a quote and author in it, we will store it in our list of quotes, and start serving it up to future requests.
+
+If we can't process the request, we return an error describing what went wrong.
+
+The details of exactly how we understand the request aren't important. The important thing is that we _are_ taking information from the request, and are then modifying the `quotes` array.
+
+> [!NOTE]
+> **State** is a general term that is used for related but different things in different contexts.
+>
+> State almost always refers to information that we store, which may change.
+
+Because we're _modifying_ the `quotes` array, it is now state.
diff --git a/common-content/en/module/decomposition/data-validation/index.md b/common-content/en/module/decomposition/data-validation/index.md
new file mode 100644
index 000000000..a258c3589
--- /dev/null
+++ b/common-content/en/module/decomposition/data-validation/index.md
@@ -0,0 +1,27 @@
++++
+title = "Data validation"
+headless = true
+time = 60
+facilitation = false
+emoji= "🔎"
+hidden_from_overview = true
+objectives = [
+ "Explain the trade-offs of doing validation on the frontend or backend.",
+]
++++
+
+If we only do the validation on the backend, the user won't know anything is wrong until they submit the form.
+
+If we only do validation on the frontend, it would be possible for users to add invalid quotes by using `curl` themselves, or building their own frontend.
+
+So we normally do validation _twice_ - once in the frontend to give fast feedback (e.g. by adding the `required` attribute to an `` tag), and once in the backend (to make sure no one can sneak bad data to us).
+
+{{}}
+Add validation that authors and quotes are both non-empty, to both your frontend, and your backend.
+
+Make sure when someone tries to post an empty quote, make sure they quickly know what's wrong.
+
+Make sure if someone tries to use `curl` to post an empty quote, it doesn't get saved either.
+
+If the backend rejects a request from the frontend, we should show the user why.
+{{}}
diff --git a/common-content/en/module/decomposition/deploy-trivial-frontend-and-backend/index.md b/common-content/en/module/decomposition/deploy-trivial-frontend-and-backend/index.md
new file mode 100644
index 000000000..56ad69a74
--- /dev/null
+++ b/common-content/en/module/decomposition/deploy-trivial-frontend-and-backend/index.md
@@ -0,0 +1,39 @@
++++
+title = "Deploy a frontend and backend"
+headless = true
+time = 60
+facilitation = false
+emoji= "📖"
+objectives = [
+ "Deploy a frontend and backend",
+ "Configure a frontend to talk to a specific deployed backend",
+]
++++
+
+Websites tend to only be useful if they're running somewhere that people can access them.
+
+We need to deploy our frontend and our backend on the internet so that people can use them.
+
+And they need to know how to talk to each other. In your frontend you probably hard-coded your `fetch` to fetch from `http://127.0.0.1:3000`.
+
+First let's deploy our backend somewhere. Then when we know its address, we can update our frontend to talk to our deployed backend, and then deploy it too.
+
+{{}}
+We also need to store our frontend and backend somewhere to deploy them from:
+1. Make a new Git repository on GitHub.
+2. Make a directory called `frontend` in the repository, and move your frontend files there.
+3. Make a directory called `backend` in the repository, and move your backend files there.
+4. Commit your files, and push them to your GitHub repository.
+{{}}
+
+{{}}
+Follow [the Deploying to fly.io guide](/guides/deploying/flyio/setup/) to deploy your backend.
+{{}}
+
+{{}}
+Update your frontend to talk to your deployed backend when it uses `fetch`.
+
+Follow [the Deploying to Netlify guide](/guides/deploying/netlify/) to deploy your frontend.
+{{}}
+
+Make sure you can use your frontend which was deployed to Netlify. Try adding a quote, and make sure it shows up in your frontend.
diff --git a/common-content/en/module/decomposition/deploy-trivial-frontend-backend-database/index.md b/common-content/en/module/decomposition/deploy-trivial-frontend-backend-database/index.md
deleted file mode 100644
index f1fcde936..000000000
--- a/common-content/en/module/decomposition/deploy-trivial-frontend-backend-database/index.md
+++ /dev/null
@@ -1,13 +0,0 @@
-+++
-title = "Deploy a frontend, backend, and database"
-headless = true
-time = 30
-facilitation = false
-emoji= "📖"
-[objectives]
- 1="Deploy a frontend, backend, and database"
- 2="Configure a frontend to talk to a specific deployed backend"
- 3="Configure a backend to talk to a specific deployed database"
-+++
-
-### Limitations of frontends
diff --git a/common-content/en/module/decomposition/design-trivial-frontend-and-backend/index.md b/common-content/en/module/decomposition/design-trivial-frontend-and-backend/index.md
new file mode 100644
index 000000000..ce4c0818e
--- /dev/null
+++ b/common-content/en/module/decomposition/design-trivial-frontend-and-backend/index.md
@@ -0,0 +1,49 @@
++++
+title = "Design a frontend and backend"
+headless = true
+time = 30
+facilitation = false
+emoji= "📖"
+[objectives]
+ 1="Design a frontend and backend which can communicate with each other"
++++
+
+We're going to take a frontend you've already made, and overcome one of its limitations by adding a backend.
+
+You should already have built a quote generator frontend before. The quote generator you already made used a static array of quotes that were {{}}Hard-coding is when we write the exact data in the source code of our programme, rather than fetching it from some data source or generating it.{{}} in the frontend.
+
+We will add some extra functionality to our quote generator. Users will be able to add their own quotes, and then other users on other computers will be able to see the quotes that were added.
+
+Because we want to be able to store data across computers and users, we know we will need a backend.
+
+Because we want to be able to change what data we're storing, we know our backend will need to be stateful.
+
+Because we don't want to have to learn about databases right now, we're going to accept the limitation that when we restart our server, we will lose any added quotes.
+
+### Communication protocols
+
+Before we get started, we should agree how our frontend and backend are going to talk to each other.
+
+The example stateful backend we looked at before exposed this {{}}An API - Application Programming Interface - is a description of how one program can interact with another.{{}}:
+
+| Path | Method | Body | Example request body | Response body | Example response body |
+| ---- | ------ | ---- | -------------------- | ------------- | --------------------- |
+| `/` | `GET` | | | A string: A quote (in quotation marks), then a dash and the author of the quote. | `"I should have been more kind." -Clive James` |
+| `/` | `POST` | JSON-serialised object with two keys: "quote" and "author", both of which contain strings. | `{"author": "Ibrahim", "quote": "Hello"}` | The string "ok" if successful, or a string describing an error. | `ok` |
+
+This API is asymmetric:
+
+When you POST information to it, you post structured information - the backend can easily tell which part is the author and which is the quote.
+
+When you GET information from it, the information is less structured - the server has already formatted the author and quote into a string.
+
+Pre-formatting the data may be convenient for the frontend if the backend knows exactly how it will be presented. But it takes away some flexibility from the frontend. If the frontend wanted to show the quote in italics, or the author in bold, this would be hard, because it would need to split the formatted string back up into its constituent parts.
+
+We probably instead want to change our GET endpoint to also return structured information, which the frontend can choose to format how it wants:
+
+| Path | Method | Body | Example request body | Response body | Example response body |
+| ---- | ------ | ---- | -------------------- | ------------- | --------------------- |
+| `/` | `GET` | | | JSON-serialised object with two keys, "quote" and "author", both of which contain strings. | `{"author": "Clive James", "quote": "I should have been more kind."}` |
+| `/` | `POST` | JSON-serialised object with two keys: "quote" and "author", both of which contain strings. | `{"author": "Ibrahim", "quote": "Hello"}` | The string "ok" if successful, or a string describing an error. | `ok` |
+
+We could also design different APIs for our frontend and backend to communicate - the important thing is that they agree on what API we will have.
diff --git a/common-content/en/module/decomposition/example-backend/index.md b/common-content/en/module/decomposition/example-backend/index.md
new file mode 100644
index 000000000..52bbba51c
--- /dev/null
+++ b/common-content/en/module/decomposition/example-backend/index.md
@@ -0,0 +1,94 @@
++++
+title = "Example backend"
+headless = true
+time = 30
+facilitation = false
+hide_from_overview = true
+emoji= "📖"
+objectives = [
+ "Run a backend using Express and NodeJS.",
+]
++++
+
+Here is a small backend written in JavaScript:
+
+```js
+import express from "express";
+
+const app = express();
+const port = 3000;
+
+const quotes = [
+ {
+ quote: "Either write something worth reading or do something worth writing.",
+ author: "Benjamin Franklin",
+ },
+ {
+ quote: "I should have been more kind.",
+ author: "Clive James",
+ },
+];
+
+function pickRandomQuote() {
+ const index = Math.floor(Math.random() * quotes.length);
+ return quotes[index];
+}
+
+app.get("/", (req, res) => {
+ console.error("Received a request for a quote");
+ const quote = pickRandomQuote();
+ res.send(`"${quote.quote}" -${quote.author}`);
+});
+
+app.listen(port, () => {
+ console.error(`Quote server listening on port ${port}`);
+});
+```
+
+If you save it in a file, make sure there's a `package.json` file in the same directory, `npm install express`, and run the file, it will start a server listening on TCP port 3000.
+
+Express is a library which lets you listen for HTTP requests, and describe how you want to respond to them with callback functions.
+
+Only two things in this file are new to us:
+
+```js
+app.listen(port, () => {
+ console.error(`Quote server listening on port ${port}`);
+});
+```
+
+This code tells Express to listen for incoming HTTP requests on the port specified by the `port` variable (and also to log to stderr saying it's doing so).
+
+```js
+app.get("/", (req, res) => {
+ console.error("Received a request for a quote");
+ const quote = pickRandomQuote();
+ res.send(`"${quote.quote}" -${quote.author}`);
+});
+```
+
+This code is instructing Express how to handle a request. It says "If you get a GET request for the path `/`, call this callback function".
+
+What this callback function does is pick a random quote from an array, format it as a string, and send it as the body of an HTTP response. It also logs to stderr when it does this.
+
+{{}}
+Run this code on your computer.
+
+Open your web browser and visit [http://127.0.0.1:3000](http://127.0.0.1:3000)
+
+You should see a quote displayed.
+
+Refresh the page a few times. You should see the quote isn't always the same.
+
+Look at the stderr of your `node` process. What do you see?
+
+Make sure you understand how your web browser is talking to your server, and why you're seeing what you see.
+{{}}
+
+{{}}
+We can also use the `curl` command line tool to make an HTTP request from our terminal.
+
+Try running: `curl http://127.0.0.1:3000` and see what it outputs.
+
+Try running: `curl -v http://127.0.0.1:3000` - try to explain what every line that was printed means.
+{{}}
diff --git a/common-content/en/module/decomposition/frontend-limitations-cross-user-interaction/index.md b/common-content/en/module/decomposition/frontend-limitations-cross-user-interaction/index.md
new file mode 100644
index 000000000..f414b7606
--- /dev/null
+++ b/common-content/en/module/decomposition/frontend-limitations-cross-user-interaction/index.md
@@ -0,0 +1,31 @@
++++
+title = "Limited cross-user interaction"
+headless = true
+time = 10
+facilitation = false
+emoji= "📖"
+hide_from_overview = true
+objectives = [
+ "Explain why sharing state across users may require us to write (or use) a backend.",
+]
++++
+
+We know if we want to share information between different computers, we need some backend to store that information so that the other computer can ask for it.
+
+When we have multiple users, we have the same problem.
+
+Imagine we're building a chat website, where multiple people can visit and talk with each other.
+
+We couldn't build this if we only had a static frontend. When one user sends a message, other users wouldn't have any way of knowing it was sent. The message doesn't _go_ anywhere, it stays in the sender's browser.
+
+If we want different users to be able to interact in any way, we need a backend they can interact through.
+
+There are lots of examples of interaction, e.g.
+* Live chat
+* Seeing each other's posts
+* Liking someone else's picture
+* Seeing what someone else wrote in a list
+
+All of these things require storing information from one user somewhere, so that another user can ask for it.
+
+On websites, users don't interact directly with each other. They interact with a backend.
diff --git a/common-content/en/module/decomposition/frontend-limitations-information/index.md b/common-content/en/module/decomposition/frontend-limitations-information/index.md
new file mode 100644
index 000000000..0f4911a62
--- /dev/null
+++ b/common-content/en/module/decomposition/frontend-limitations-information/index.md
@@ -0,0 +1,56 @@
++++
+title = "Limited information"
+headless = true
+time = 20
+facilitation = false
+emoji= "📖"
+hide_from_overview = true
+objectives = [
+ "Identify strategies for updating information in a static website.",
+ "Explain why needing dynamic information may require us to write a backend.",
+]
++++
+
+{{}}
+
+When we ask a server to serve us a static frontend, it can only give us the files it knows about.
+
+Imagine we wanted to display the current weather in Rome on our website.
+
+How could the server know that information? How could it give it to the user?
+
+We know that our static frontend can only serve the files it knows about. So someone will need to save in a file on the server what the weather currently is.
+
+There are a number of ways we could achieve this, e.g.:
+* We could never update our file, and just serve out of date data.
+* Someone with access to the server could connect to it and edit the file.
+* We could set up some automated process which checks the weather, generates a new file, and uploads the new version to the server.
+
+If we only host a static frontend, it is possible for us to update the information in it. But it is limiting.
+
+Another approach, is that our frontend could know how to request information from a backend. That could be a backend we run, or one that someone else runs.
+
+We have seen this when we have used `fetch`. `fetch` allows our static frontend to ask some other server for information.
+
+We might not know whether the server we're asking for information is static or dynamic:
+
+It could:
+* Only serve information it already knows in its own files (i.e. it is a static frontend itself).
+* Look up the information from a database which may change.
+* Ask yet another server for information.
+
+Even if we use `fetch` from our frontend, it is still a static frontend. We still serve it to users by giving them the same static files.
+
+If someone else already runs a server we can `fetch` from, we can probably keep our own website just being a static frontend.
+
+But if we need more dynamic information, and there isn't a convenient place we can fetch it from, we may need to write/host something we can `fetch` from ourselves. We call this a backend.
+
+{{}}
diff --git a/common-content/en/module/decomposition/frontend-limitations-intro/index.md b/common-content/en/module/decomposition/frontend-limitations-intro/index.md
new file mode 100644
index 000000000..b4296f4f8
--- /dev/null
+++ b/common-content/en/module/decomposition/frontend-limitations-intro/index.md
@@ -0,0 +1,36 @@
++++
+title = "Limitations of frontends"
+headless = true
+time = 5
+facilitation = false
+emoji= "📖"
+objectives = [
+ "Explain what can't be achieved with just a frontend",
+]
++++
+
+You've made static websites, both as part of the "world-wide web" reading, and in previous courses/modules.
+
+These were HTML pages, which perhaps included some JavaScript. The JavaScript made them interactive. But they were still static.
+
+A static website is one where your web browser requests some files from a server, and all the web server does is hand over the files.
+
+From that point on, your web browser works on its own. Any event handlers, timers, etc are processed by the web browser.
+
+And the job of the server was just to hand over files. Those files will probably be the same, no matter what user asked for them, or what computer they were using.
+
+We often call these static files a **frontend**.
+
+And we can build impressive, useful websites with just a frontend. We can include images, videos, and sounds. We can react to user input. We can change what we show on the page. We can even use things like [the Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) to store data so that if you leave the page and come back, your data is still there.
+
+But frontends also have limitations.
+
+### Limitations of a frontend
+
+Four major limitations of a website which only has a frontend are:
+1. We only have access to information we knew when we made the frontend.
+2. We can't share information across computers.
+3. We can't interact with other users.
+4. Everything is public.
+
+Let's talk about each of these.
diff --git a/common-content/en/module/decomposition/frontend-limitations-public/index.md b/common-content/en/module/decomposition/frontend-limitations-public/index.md
new file mode 100644
index 000000000..80de8d3d5
--- /dev/null
+++ b/common-content/en/module/decomposition/frontend-limitations-public/index.md
@@ -0,0 +1,30 @@
++++
+title = "Everything is public"
+headless = true
+time = 15
+facilitation = false
+emoji= "📖"
+hide_from_overview = true
+objectives = [
+ "Explain why needing secrets may require backend.",
+]
++++
+
+A frontend is served, completely, to a web browser which then interprets it.
+
+This causes problems if there is information we don't want to give to the user.
+
+An example is a password.
+
+Imagine if we had a website, and wanted users to enter a password in order to access it.
+
+Somewhere, we'll need to write some logic which compares the password the user entered with the actual password.
+
+If we _only_ have a frontend, this poses two problems:
+
+1. We need to give the web browser the actual password, in order for it to be able to compare them. And everything we give to the web browser, a user could also see if they wanted (e.g. by monitoring their network traffic).
+2. A website can't trust a web browser. Once a website hands information over to a client, it doesn't know what the client will do with it.
+
+ For instance if a frontend has three pieces of data, but wants you to tick a box before it shows them to you, the user can probably bypass that agreement by running some custom code in the web inspector console.
+
+ If we need to _guarantee_ the user ticked a box before being shown data, we can't include the data in our frontend until _after_ the box has been ticked. Which means we need to ask a backend for it.
diff --git a/common-content/en/module/decomposition/frontend-limitations-sharing-across-computers/index.md b/common-content/en/module/decomposition/frontend-limitations-sharing-across-computers/index.md
new file mode 100644
index 000000000..c70e82bb9
--- /dev/null
+++ b/common-content/en/module/decomposition/frontend-limitations-sharing-across-computers/index.md
@@ -0,0 +1,43 @@
++++
+title = "Limited sharing across computers"
+headless = true
+time = 20
+facilitation = false
+emoji= "📖"
+hide_from_overview = true
+objectives = [
+ "Explain why sharing state across computers may require us to write (or use) a backend.",
+]
++++
+
+The [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) allows a website to store data so that if you leave a website, and go back to it, it remembers some things.
+
+{{}}
+
+A frontend has no way of bringing your data with you when you move between different computers.
+
+Imagine you're using a to do list website on your computer, which only has a frontend. You add an item to the list. We can use the Web Storage API to make sure if you close the tab, and go back to the list, the item is still there.
+
+But if you open the website on your phone, you won't see that item. Because the Web Storage API only stores the information locally in your web browser - on your computer.
+
+This is one of the limitations of a static frontend - on its own, it can't share information across devices.
+
+If we want to share information across devices, we'll need some backend to store that information, and share it.
+
+One computer needs to tell the backend to store it, so that the other computer can ask the backend for the information.
+
+We could write our own backend, or we could use an existing service like [Firebase](https://firebase.google.com/) which provides backends for things like authentication and data storage without us needing to run a backend ourselves.
+
+But if we want to share information between devices, we need to use _some_ backend.
+
+Any website that lets you log in will involve a backend.
+
+{{}}
diff --git a/common-content/en/module/decomposition/limitations-of-frontends/index.md b/common-content/en/module/decomposition/limitations-of-frontends/index.md
deleted file mode 100644
index 34309e1b0..000000000
--- a/common-content/en/module/decomposition/limitations-of-frontends/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
-+++
-title = "Limitations of frontends"
-headless = true
-time = 30
-facilitation = false
-emoji= "📖"
-[objectives]
- 1="Explain what can't be achieved with just a frontend"
-+++
-
-### Limitations of frontends
diff --git a/common-content/en/module/decomposition/read-about-the-internet/index.md b/common-content/en/module/decomposition/read-about-the-internet/index.md
index dbb833115..d57405fe0 100644
--- a/common-content/en/module/decomposition/read-about-the-internet/index.md
+++ b/common-content/en/module/decomposition/read-about-the-internet/index.md
@@ -1,13 +1,51 @@
+++
title = "Read about the Internet"
headless = true
-time = 30
+time = 60
facilitation = false
emoji= "📖"
-[objectives]
- 1="TODO"
+objectives = [
+ "Define the terms 'host', 'client', and 'server'.",
+ "Define the terms 'request' and 'response'.",
+ "Explain the responsibilities/guarantees of four network layers: Link, Internet, Transport, Application.",
+ "Explain the purpose of a TCP port.",
+]
+++
### Read about the Internet
-Read chapter 11 of How Computers Really Work
+{{}}
+Read chapter 11 of How Computers Really Work.
+
+Do every exercise listed in the chapters.
+
+Do the following projects 29, 30, 31, 32, 34, 35.
+
+For project 29, note that if you're on macOS, you should use `en0` not `eth0`, and you will need to install `arp-scan` by running `brew install arp-scan`.
+{{}}
+
+Check you have achieved each learning objective listed on this page.
+
+{{}}
+
+{{}}
+
+{{}}
+
+{{}}
diff --git a/common-content/en/module/decomposition/read-about-the-www/index.md b/common-content/en/module/decomposition/read-about-the-www/index.md
index 593c27b2f..580058f7c 100644
--- a/common-content/en/module/decomposition/read-about-the-www/index.md
+++ b/common-content/en/module/decomposition/read-about-the-www/index.md
@@ -1,13 +1,66 @@
+++
title = "Read about the world-wide web"
headless = true
-time = 30
+time = 75
facilitation = false
emoji= "📖"
-[objectives]
- 1="TODO"
+objectives = [
+ "Identify the components of a URL.",
+ "Manually parse an HTTP request into a method, path, protocol, headers, and body.",
+ "Manually parse an HTTP response into protocol, status code, headers, and body.",
+ "Describe the request-response pattern of a web browser interacting with a server.",
+ "Compare static and dynamic web servers."
+]
+++
-### Read about the world-wide web
+{{}}
+Read chapter 12 of How Computers Really Work.
-Read chapter 12 of How Computers Really Work
+In the "Languages of the Web" section, you can skip the following sub-sections if you want because you probably already know what they say:
+* Structuring the Web with HTML
+* Styling the Web with CSS
+* Scripting the Web with JavaScript
+
+But you should still read the Structuring the Web's Data with JSON and XML sub-section.
+
+Do every exercise listed in the chapters.
+
+Do projects 36-40. You can do these on any Unix-based computer.
+{{}}
+
+{{}}
+
+{{}}
+
+{{}}
+In the following HTTP request, identify the path being requested, and the request headers:
+
+```
+GET /id HTTP/1.1
+Host: www.example.com
+User-Agent: curl/8.1.2
+Accept: */*
+```
+{{}}
+
+{{}}
+In the following HTTP response, identify the status code, the response headers, and the response body:
+
+```
+HTTP/1.1 200 OK
+Expires: -1
+Cache-Control: private, max-age=0
+Content-Type: application/json
+Content-Length: 10
+
+{"id": 17}
+```
+{{}}
diff --git a/common-content/en/module/decomposition/responsibilities-of-frontends-backends-databases/index.md b/common-content/en/module/decomposition/responsibilities-of-frontends-backends-databases/index.md
index 35fe92f63..76ee697c6 100644
--- a/common-content/en/module/decomposition/responsibilities-of-frontends-backends-databases/index.md
+++ b/common-content/en/module/decomposition/responsibilities-of-frontends-backends-databases/index.md
@@ -1,12 +1,36 @@
+++
title = "Responsibilities of frontends, backends, and databases"
headless = true
-time = 30
+time = 15
facilitation = false
emoji= "📖"
[objectives]
1="Categorise functionality into where it should belong"
- 2="Describe limitations of backends without reliable data persistence"
+++
-### Responsibilities of frontends, backends, and databases
+When trying to decide which components we need, and in which component functionality should live, it's useful to think about the capabilities and limitations of each.
+
+A few guiding principles:
+* If you need multiple users or computers to share some information, you need a backend to coordinate this.
+* If you need your data to be reliably persisted, you need a database (or other storage component) to store it.
+* If you need things to respond very quickly to a user interaction, you probably want to do this in a frontend.
+
+Try to identify which components we would need for each of these applications:
+
+{{}}
+
+{{}}
+
+{{}}
diff --git a/common-content/en/module/decomposition/what-is-a-backend/index.md b/common-content/en/module/decomposition/what-is-a-backend/index.md
new file mode 100644
index 000000000..962f167d6
--- /dev/null
+++ b/common-content/en/module/decomposition/what-is-a-backend/index.md
@@ -0,0 +1,36 @@
++++
+title = "What is a backend?"
+headless = true
+time = 20
+facilitation = false
+emoji= "📖"
+objectives = [
+ "Define the backend of a website.",
+ "Explain the terms _client_ and _server_."
+]
++++
+
+We know that a frontend is a collection of static files that can be handed to a web browser, and which the web browser will evaluate.
+
+A backend is a program which a frontend can communicate with. The frontend can ask it for (or tell it) information, and the backend can respond.
+
+A backend can be written in any programming language. It doesn't need to be the same programming language as the frontend is written in (but it can be).
+
+Most backends are long-running. They are programs that keep running for a long time, waiting for a frontend to communicate with them. This is important - if the program wasn't running, the frontend wouldn't be able to communicate with it.
+
+Not all websites have a backend. There are lots of websites which are just a frontend. But if we need to overcome any of the limitations we've explored, our system will need a backend. Sometimes we need to write the backend ourselves. Other times we can use one someone else has written.
+
+There's nothing special about a backend. It's just a program that can be communicated with. You've already written plenty of programs.
+
+A backend is just a program, but it gets its input from an HTTP request over the network, instead of from a command line flag or file. And it writes its output in an HTTP response over the network, instead of to stdout or a file.
+
+> [!NOTE]
+> Backend and Frontend are terms that get used differently in different contexts.
+>
+> In this section, we're mostly talking about websites here, but other software systems also have backends and frontends.
+>
+> Other similar terms are **client** and **server**. People often talk about a web browser, or a frontend, as being a client, and a backend as being a server.
+>
+> In general a "server" is something waiting to be communicated with, and a "client" is something that will communicate with it.
+>
+> Sometimes one program acts as both a client and a server! A web browser may ask (be a client) a backend (a server) for some information. And that backend may ask (be a client) _another_ backend (a server) for information to help it answer the question.
diff --git a/common-content/en/module/decomposition/write-trivial-frontend-and-backend/index.md b/common-content/en/module/decomposition/write-trivial-frontend-and-backend/index.md
new file mode 100644
index 000000000..6df3c67f5
--- /dev/null
+++ b/common-content/en/module/decomposition/write-trivial-frontend-and-backend/index.md
@@ -0,0 +1,38 @@
++++
+title = "Write a frontend and backend"
+headless = true
+time = 60
+facilitation = false
+emoji= "📖"
+[objectives]
+ 1="Run a frontend and backend which can communicate with each other"
++++
+
+{{}}
+Take the example stateful backend we already saw, and adapt it so that it serves the API we designed.
+
+You can test it by running your backend and making the following requests to it:
+
+* `curl http://127.0.0.1:3000/`
+* `curl -X POST --data '{"author": "Ibrahim", "quote": "Hello"}' http://127.0.0.1:3000/`
+
+Make sure the responses you get match the API specification we designed.
+{{}}
+
+Now that you have a backend which meets the API specification we designed, let's make a frontend that can talk to it.
+
+You are free to borrow parts of your previous quote generator website, or make one from scratch.
+
+{{}}
+Make a quote generator frontend which fetches quotes from your backend.
+
+Your frontend should not have any quotes hard-coded in it. It should make a `fetch` request to your backend, and render whatever quote was returned.
+
+It should have a button to fetch a new quote, which should fetch and render a different quote.
+
+It can be as ugly as you want, the purpose of this exercise is not to make a beautiful UI, but to see how things connect.
+{{}}
+
+Because our frontend talks to our backend, it has fewer limitations than the previous quote generator you made.
+
+We don't currently expose the capability to add new quotes in our frontend. But our backend _does_ allow this. A user who knew where our backend was running, and what API it exposes, could use `curl` to add quotes, or could even build their own frontend for adding quotes.
diff --git a/common-content/en/module/decomposition/write-trivial-frontend-backend-database/index.md b/common-content/en/module/decomposition/write-trivial-frontend-backend-database/index.md
deleted file mode 100644
index 5ed02f99a..000000000
--- a/common-content/en/module/decomposition/write-trivial-frontend-backend-database/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
-+++
-title = "Write a frontend, backend, and database"
-headless = true
-time = 30
-facilitation = false
-emoji= "📖"
-[objectives]
- 1="Run a frontend, backend, and database which can communicate with each other"
-+++
-
-### Write a frontend, backend, and database
diff --git a/org-cyf-sdc/content/decomposition/sprints/1/backlog/index.md b/org-cyf-sdc/content/decomposition/sprints/1/backlog/index.md
index d23d2978e..e7ee73089 100644
--- a/org-cyf-sdc/content/decomposition/sprints/1/backlog/index.md
+++ b/org-cyf-sdc/content/decomposition/sprints/1/backlog/index.md
@@ -4,6 +4,6 @@ layout = 'backlog'
emoji= '🥞'
menu_level = ['sprint']
weight = 2
-backlog= 'Module-Template'
+backlog= 'Module-Decomposition'
backlog_filter='📅 Sprint 1'
+++
diff --git a/org-cyf-sdc/content/decomposition/sprints/1/prep/index.md b/org-cyf-sdc/content/decomposition/sprints/1/prep/index.md
index cbaf002b3..4accb3b90 100644
--- a/org-cyf-sdc/content/decomposition/sprints/1/prep/index.md
+++ b/org-cyf-sdc/content/decomposition/sprints/1/prep/index.md
@@ -6,18 +6,54 @@ emoji= '🧑🏾💻'
menu_level = ['sprint']
weight = 1
[[blocks]]
-title="Read about the Internet"
+name="Read about the Internet"
src="module/decomposition/read-about-the-internet"
[[blocks]]
-title="Limitations of frontends"
-src="module/decomposition/limitations-of-frontends"
+name="Read about the world-wide web"
+src="module/decomposition/read-about-the-www"
[[blocks]]
-title="Responsibilities of frontends, backends, and databases"
+name="Limitations of frontends"
+src="module/decomposition/frontend-limitations-intro"
+[[blocks]]
+name="Limited information"
+src="module/decomposition/frontend-limitations-information"
+[[blocks]]
+name="Limited sharing across computers"
+src="module/decomposition/frontend-limitations-sharing-across-computers"
+[[blocks]]
+name="Limited cross-user interaction"
+src="module/decomposition/frontend-limitations-cross-user-interaction"
+[[blocks]]
+name="Everything is public"
+src="module/decomposition/frontend-limitations-public"
+[[blocks]]
+name="What is a backend?"
+src="module/decomposition/what-is-a-backend"
+[[blocks]]
+name="Example backend"
+src="module/decomposition/example-backend"
+[[blocks]]
+name="Backend statefulness"
+src="module/decomposition/backend-statefulness"
+[[blocks]]
+name="Limitations of backends"
+src="module/decomposition/backend-limitations"
+[[blocks]]
+name="Responsibilities of frontends, backends, and databases"
src="module/decomposition/responsibilities-of-frontends-backends-databases"
[[blocks]]
-title="Write a frontend, backend, and database"
-src="module/decomposition/write-trivial-frontend-backend-database"
+name="Design a frontend and backend"
+src="module/decomposition/design-trivial-frontend-and-backend"
+[[blocks]]
+name="Write a frontend and backend"
+src="module/decomposition/write-trivial-frontend-and-backend"
+[[blocks]]
+name="Adding quotes"
+src="module/decomposition/adding-quotes"
+[[blocks]]
+name="Data validation"
+src="module/decomposition/data-validation"
[[blocks]]
-title="Deploy a frontend, backend, and database"
-src="module/decomposition/deploy-trivial-frontend-backend-database"
+name="Deploy a frontend and backend"
+src="module/decomposition/deploy-trivial-frontend-and-backend"
+++
diff --git a/org-cyf-sdc/content/decomposition/sprints/2/prep/index.md b/org-cyf-sdc/content/decomposition/sprints/2/prep/index.md
index f4c20887f..a634ee4cd 100644
--- a/org-cyf-sdc/content/decomposition/sprints/2/prep/index.md
+++ b/org-cyf-sdc/content/decomposition/sprints/2/prep/index.md
@@ -6,9 +6,6 @@ emoji= '🧑🏾💻'
menu_level = ['sprint']
weight = 1
[[blocks]]
-title="Read about the world-wide web"
-src="module/decomposition/read-about-the-www"
-[[blocks]]
title = "Making a non-live chat application"
src = "module/decomposition/non-live-chat-application"
[[blocks]]