Collapsed
Shopfront is the storefront for Acme's online shop. Shoppers browse products and track orders; staff use the admin screens to manage the catalog. Sources: README.md, client/src/routes.jsx, server/index.js.
Who uses it
- Shoppers: browse the catalog, place orders, and look up an order by id
- Staff: add products through the admin catalog editor (behind the newAdmin flag)
What it does
- Lists products and product details from PostgreSQL
- Creates orders from a cart and rejects empty carts
- Lets staff create products through
/api/admin
Where it runs
- Web client: a React single-page app built with Vite (
client/) - API: one Express server on port 4000, shipped as a Docker image (
Dockerfile) - Database: PostgreSQL, reached through
DATABASE_URL
Collapsed
What people can do in Shopfront, by area. flag marks features behind a feature flag. Routes match the Site map.
| Feature | What it is | Route | Code | Rollout |
|---|---|---|---|---|
| Shopping | ||||
| Home | Landing page of the storefront. | / | client/src/pages/Home.jsx | — |
| Product catalog | Lists every product; the API also serves one product by id. | /products · GET /api/products[/:id] | client/src/pages/Products.jsx, server/routes/products.js | — |
| Place an order | Validates the cart, stores the order, and returns its id. An empty cart gets a 400. | POST /api/orders | server/routes/orders.js (createOrder) | — |
| Order status | Shows one order by id. | /orders/:id · GET /api/orders/:id | client/src/pages/Order.jsx, server/routes/orders.js | — |
| Staff | ||||
| Admin catalog editor | Staff screen for managing products. The create endpoint currently echoes the request body back and doesn't write to the database. | /admin · POST /api/admin/products | client/src/pages/Admin.jsx, server/routes/products.js | newAdmin |
Collapsed
Every route in the app and how you reach it. Sources: client/src/routes.jsx, server/index.js.
| Route | Opens | Shown when |
|---|---|---|
/ | Home | Always |
/products | Product catalog | Always |
/orders/:id | Order status (linked or typed by id; there's no nav menu) | Always |
/admin | Admin catalog editor | Only when VITE_FLAG_NEW_ADMIN=true at build time; otherwise the route doesn't exist |
/ Home
/productsproduct catalog/orders/:idone order/adminstaff catalog editor · newAdmin
/api Express API (server/index.js)
GET /api/products·GET /api/products/:idPOST /api/orders·GET /api/orders/:idPOST /api/admin/productsstaff only (requireStaff)
Collapsed
A React client calls one Express API, which reads and writes PostgreSQL (follow one request through it in the Express deep dive and the PostgreSQL guide). GitHub Actions tests every push and deploys main as a Docker image.
Staff access: /api/admin is guarded by requireStaff in server/auth.js, which only checks that an x-staff-role header is present. The comment above it says a company SSO proxy sets a signed session cookie, but the code doesn't read a cookie. Confirm which the proxy actually sends.
Collapsed
What Shopfront is built with, by layer. Versions come from package.json (ranges, no lockfile), the Dockerfile, and .github/workflows/ci.yml.
| Technology | What it does here | Where |
|---|---|---|
| Frontend | ||
| React ^18.3.1 | UI for every page | client/src/pages/ |
| React Router ^6.23.0 | Client routes via createBrowserRouter | client/src/routes.jsx |
| Vite ^5.2.0 | Dev server (:5173) and production build; injects VITE_* flags | client/ |
| Backend | ||
| Node.js 20 | Runtime in the production image | Dockerfile |
| Express ^4.19.2 | HTTP API and routing. New to it? Read the Express deep dive. | server/ |
| Data & storage | ||
| PostgreSQL | Products and orders tables. New to it? Read the PostgreSQL guide. | server/routes/ |
| node-postgres (pg) ^8.11.0 | Connection pool from DATABASE_URL | server/db.js |
| Tooling & CI | ||
| Jest ^29.7.0 + Supertest ^7.0.0 | API tests against the Express app in memory | test/ |
| ESLint ^9.0.0 | Linting, run in CI | package.json |
| concurrently ^8.2.0 | Runs API and client together in npm run dev | package.json |
| GitHub Actions | Lint, test, and deploy pipeline | .github/workflows/ci.yml |
| Docker | Production image: installs, builds the client, starts the API | Dockerfile |
Collapsed
Where Shopfront runs and how a change gets to production. Sources: .github/workflows/ci.yml, Dockerfile.
| Environment | URL / project | Data and who uses it |
|---|---|---|
| Local | localhost:5173 (client), localhost:4000 (API) | Your own PostgreSQL via DATABASE_URL |
| Staging | Not in the repo | The deploy step mentions a staging registry; the URL and data aren't defined in code |
| Production | Not in the repo | Promoted from staging by tag, according to the deploy step |
| # | Step | What happens and what can stop it |
|---|---|---|
| 1 | Push any branch | The test job runs npm ci, npm run lint, and npm test. A failure blocks deploy. |
| 2 | Merge to main | The deploy job runs only on refs/heads/main and only after test passes. |
| 3 | Build and push the image | Placeholder: today the step only echos “docker build + push to the staging registry, then promote to production by tag”. The real deploy happens outside this repo. |
Collapsed
How to get Shopfront running locally and the day-to-day loop. Every command here was checked against the repo's scripts.
| Step | Command / notes |
|---|---|
| Install Node 20 | Matches FROM node:20-slim in the Dockerfile |
| Install dependencies | npm install |
| Point at a database | Set DATABASE_URL to a local PostgreSQL with products and orders tables. There are no migrations or seeds in the repo. |
npm install
export DATABASE_URL=postgres://localhost:5432/shopfront # your local database
npm run dev # API on :4000, client on :5173| Task | Command |
|---|---|
| Start the app | npm run dev (API on :4000, client on :5173) |
| Turn on the admin screen | VITE_FLAG_NEW_ADMIN=true npm run dev |
| Lint | npm run lint |
| Test | npm test |
Collapsed
Which kind of test to write, where it goes, and how to run it. Sources: test/, package.json, .github/workflows/ci.yml.
| Layer | Tool | Good for | Run one |
|---|---|---|---|
| API | Jest + Supertest | Request validation and responses. Imports the Express app without starting a server (test/orders.test.js). | npx jest test/orders.test.js |
| Client | None yet | No component or end-to-end tests exist. | — |
CI runs lint and the full Jest suite on every push.
The one API test today: it imports the app and sends a request through Supertest, so no server or database is needed for this case.
const request = require('supertest');
const app = require('../server');
test('rejects an empty cart', async () => {
await request(app).post('/api/orders').send({ items: [] }).expect(400);
});Collapsed
Express is a minimal web framework for Node.js: it routes HTTP requests to your functions and gives you helpers for reading requests and sending responses. Shopfront's whole API (/api/…) is one Express 4 app. Sources: expressjs.com, Application (4.x API), server/index.js.
In one paragraph
- An Express app is a request handler for Node's HTTP server. You build it from a stack of middleware functions that run in order.
- Each middleware gets the request and response, and either answers or calls
next()to pass the request on (Using middleware). - Routers group endpoints under a path prefix, so each resource gets its own file.
The problem it solves
- Plain Node
httphands you raw requests. Express adds URL routing with parameters, body parsing, and helpers likeres.status(201).json(…). - It stays unopinionated: no ORM, no project layout, so apps choose their own (Shopfront uses
pgdirectly).
Compared with
- Node's http module: what Express wraps;
app.listen()creates the server for you (docs). - Express 5: the current major. Same model, but async errors are handled for you and route-path syntax is stricter (Migrating to 5). Shopfront is on 4.
How Shopfront uses it
- One app with JSON parsing, three routers (products, orders, admin), one auth middleware, and Postgres behind it (usage map).
- The React client doesn't call it yet; tests do, through supertest. The repo doesn't record why Express was chosen.
Collapsed
The ideas you need before reading Shopfront's server code, then each one in code. Links and examples are for Express 4, the major version Shopfront's ^4.19.2 range installs (see Versions).
| Concept | What it means | In Shopfront | Official docs |
|---|---|---|---|
| Handling a request | |||
| Middleware | A function (req, res, next) that either ends the request (sends a response) or calls next(). Doing neither leaves the request hanging. They run in the order registered. | express.json(), requireStaff | Using middleware |
| app.use vs app.get/post | app.use(path, fn) runs for any method and matches the path as a prefix; get/post match one method on that path. | server/index.js:7-10 | Application |
| Router | express.Router() is a mini-app with its own routes, mounted at a prefix with app.use. | One per resource in server/routes/ | Routing |
| Route parameters | :id in a path captures that segment into req.params.id, always a string. Query strings go to req.query. | /api/orders/:id | Routing |
| Request & response | |||
| express.json() | Parses JSON bodies (Content-Type application/json, up to 100kb) into req.body. Without a parser, req.body is undefined. Invalid JSON becomes a 400 error. | server/index.js:7 | express.json |
| Responses | res.status(code) chains with res.json(body); res.sendStatus(code) sends just the status. | Every handler | Response |
| Errors & testing | |||
| Error handling | Error middleware has four arguments, (err, req, res, next), and goes last. In Express 4, errors from async handlers aren't passed on automatically: catch them and call next(err). | Not used (see gotchas) | Error handling |
| Testing with supertest | Pass the exported app to request(app) to send real HTTP requests without starting a server yourself. | test/orders.test.js | supertest |
| Calling it: fetch | Browsers call a JSON API with fetch. It only rejects on network errors, so check response.ok for 4xx/5xx. | Not used by the client yet | MDN: Using Fetch |
Short examples adapted from the Express 4 guides, MDN, and the supertest README, using placeholder values. Compare each with the Shopfront file named under it.
Create an app, parse JSON, mount a router. Shopfront does this in server/index.js.
const express = require('express');
const app = express();
app.use(express.json()); // req.body for JSON requests
const items = express.Router();
app.use('/api/items', items); // items' routes live under /api/items
module.exports = app; // export for tests; listen elsewhereRoutes with a parameter and JSON responses. Compare server/routes/orders.js.
items.get('/:id', (req, res) => {
res.json({ id: req.params.id, q: req.query.q }); // params are strings
});
items.post('/', (req, res) => {
if (!req.body.name) return res.status(400).json({ error: 'name required' });
res.status(201).json({ name: req.body.name });
});Middleware that allows or rejects. Compare server/auth.js, which checks only that a header exists.
function requireKey(req, res, next) {
if (req.get('X-Example-Key') !== '<expected-value>') {
return res.sendStatus(403); // stop here
}
next(); // pass to the next handler
}
app.use('/api/private', requireKey, privateRouter);Async errors in Express 4. What Shopfront's handlers are missing.
items.get('/:id/details', async (req, res, next) => {
try {
const item = await loadItem(req.params.id); // your async lookup
res.json(item);
} catch (err) {
next(err); // Express 4 won't do this for you
}
});
app.use((err, req, res, next) => { // four arguments, registered last
res.status(err.status || 500).json({ error: 'something went wrong' });
});Make an API call from the browser. How a Shopfront page would place an order.
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // needed for express.json()
body: JSON.stringify({ items: [{ productId: 42, qty: 1 }] }),
});
if (!res.ok) throw new Error(`Order failed: ${res.status}`); // fetch doesn't throw on 4xx/5xx
const { id } = await res.json();Make an API call from the terminal. Against the local server only.
curl http://localhost:4000/api/products/42
curl -X POST http://localhost:4000/api/orders \
-H 'Content-Type: application/json' \
-d '{"items": [{"productId": 42, "qty": 1}]}'Test an endpoint without starting a server. Shopfront's test/orders.test.js follows this pattern.
const request = require('supertest');
const app = require('../server');
test('GET an item', async () => {
const res = await request(app).get('/api/items/42');
expect(res.status).toBe(200);
expect(res.body.id).toBe('42');
});Collapsed
Express is Shopfront's whole API server: one app, three routers, one auth middleware, and Postgres behind it, in about 6 tracked files under server/ and test/ (git ls-files server test | wc -l). The React client doesn't call the API yet. Sources: server/index.js, server/routes/, package.json.
A request passes through the middleware in the order server/index.js registers it: JSON parsing, then the router whose prefix matches, then the handler. /api/admin requests go through requireStaff first. The guided walkthrough follows steps 1–5.
| Endpoint | What it does | Where (repo path) |
|---|---|---|
| Public | ||
| GET /api/products | Every product row, as a JSON array. | server/routes/products.js:3 |
| GET /api/products/:id | One product by id; responds 200 with no product if the id doesn't exist. | server/routes/products.js:4 |
| POST /api/orders | Validates items (400 empty cart), inserts an order, returns 201 { id }. | server/routes/orders.js:4-9 |
| GET /api/orders/:id | One order by id; same no-row behavior as products. | server/routes/orders.js:10 |
| Staff (behind requireStaff) | ||
| POST /api/admin/products | Echoes the request body back with 201. Nothing is saved yet. | server/routes/products.js:5-6, server/index.js:10 |
| Piece | Role | Where |
|---|---|---|
| Server | ||
| App | Creates the app, registers middleware and routers, listens on PORT or 4000 only when run directly, and exports app for tests. | server/index.js |
| express.json() | Parses JSON request bodies into req.body for every route. | server/index.js:7 |
| Routers | express.Router() per resource, mounted at a prefix with app.use. | server/routes/products.js, server/routes/orders.js |
| requireStaff | Middleware: calls next() when the x-staff-role header is present, otherwise 403. | server/auth.js |
| Data & tests | ||
| db | A shared pg connection pool from DATABASE_URL; handlers call db.query with $1 placeholders. | server/db.js |
| supertest | Sends requests to the exported app in Jest tests, without starting a server. | test/orders.test.js |
| express | Range ^4.19.2, no lockfile (see Versions). | package.json:10 |
Collapsed
Follow one request, POST /api/orders, from the caller to the database and back. It touches every Express idea Shopfront uses. Sources: server/index.js, server/routes/orders.js, server/db.js.
Nothing in Shopfront's client calls the API yet, so the caller today is a test or a tool like curl. A browser client would send the same request with fetch:
POST /api/orders HTTP/1.1
Content-Type: application/json
{"items": [{"productId": 42, "qty": 1}]}Express runs middleware in the order it was registered. The first one, express.json(), reads the JSON body and puts it on req.body. Without it, req.body would be undefined. Concept: middleware.
const app = express();
app.use(express.json());
app.use('/api/products', products);
app.use('/api/orders', orders);
app.use('/api/admin', requireStaff, products.admin);/api/orders matches the orders router's prefix, so Express hands it the rest of the path (/). Inside the router, router.post('/', …) matches the method and path. Concept: routers.
router.post('/', createOrder);
router.get('/:id', async (req, res) => res.json((await db.query('select * from orders where id=$1', [req.params.id])).rows[0]));createOrder rejects an empty or missing cart with 400, before touching the database. Otherwise it inserts the order and responds 201 with the new id. Concept: request and response.
async function createOrder(req, res) {
if (!Array.isArray(req.body.items) || !req.body.items.length) return res.status(400).json({ error: 'empty cart' });
const { rows } = await db.query('insert into orders(items) values ($1) returning id', [JSON.stringify(req.body.items)]);
res.status(201).json({ id: rows[0].id });
}db.query runs on a shared pg pool. Values go in as $1 parameters, never pasted into the SQL string. Where the flow stops today: the repo has no schema or migrations for the orders table, and the handler has no try/catch, so a database error crashes the server process instead of returning a 500 (see Patterns & gotchas). The PostgreSQL walkthrough picks up from here.
const { Pool } = require('pg');
module.exports = new Pool({ connectionString: process.env.DATABASE_URL });Steps 1–4 run without a database, because the empty-cart check returns before any query. The existing test does exactly that:
npm test # runs test/orders.test.js: POST /api/orders with an empty cart → 400
npm run dev # API on :4000
curl -i -X POST http://localhost:4000/api/orders \
-H 'Content-Type: application/json' -d '{"items": []}' # → 400 {"error":"empty cart"}A non-empty cart reaches step 5, which needs a local Postgres in DATABASE_URL with an orders table. The repo doesn't define that table, so that part can't be run from the repo as it stands.
Collapsed
Shopfront's server is a small, conventional Express 4 app. The gotchas are mostly what it leaves out: error handling, 404s, and a real staff check. Sources: server/index.js, server/auth.js, server/routes/.
Conventions here
- One router per resource in
server/routes/, mounted under/api/<resource>inserver/index.js. - Export the app, listen only when run directly (
require.main === module), so tests can import it. - Parameterized SQL:
$1placeholders with a values array, never string concatenation. - Errors as JSON:
res.status(4xx).json({ error: '…' }). - Gate a group of routes by passing middleware to
app.usebefore the router (server/index.js:10).
Gotchas
- A failed query crashes the server. Every handler is
asyncwith notry/catch. In Express 4 a rejected promise isn't passed tonext; it's an unhandled rejection, which crashes the process on current Node versions (the Dockerfile usesnode:20-slim). See error handling. - Stack traces reach clients. No
(err, req, res, next)handler is registered andNODE_ENVisn't set anywhere in the repo, so Express's default handler sends the stack trace, e.g. for a request with invalid JSON (a 400 fromexpress.json()). - Missing rows return 200.
GET /:idsendsrows[0]even when it'sundefined, instead of a 404. - The staff check trusts a header.
requireStaffaccepts any request with anx-staff-roleheader, whatever its value. Its comment says staff come from a signed SSO cookie (server/auth.js:1-3). Safe only if a proxy always strips or sets that header; reported as a repo problem. - The admin endpoint is a stub: it echoes the body without saving (
server/routes/products.js:6).
Collapsed
The API changes you're most likely to make in Shopfront, and how to test each one with Jest and supertest (npm test). Sources: server/, test/orders.test.js, package.json.
| Task | Steps | Where |
|---|---|---|
| Add an endpoint | 1. Add router.get/post(…) to the resource's router. 2. Validate input and return res.status(…).json(…). 3. Add a supertest test. | server/routes/, test/ |
| Add a resource | 1. Create server/routes/<name>.js exporting a router. 2. Mount it in server/index.js with app.use('/api/<name>', …). | server/index.js:8-10 |
| Protect an endpoint | Mount it behind requireStaff like /api/admin, or pass the middleware to one route: router.post('/', requireStaff, handler). | server/auth.js |
| Handle async errors | Wrap the handler body in try/catch and call next(err), then register one error-handling middleware last in server/index.js. | server/index.js |
| Return 404 for a missing row | Check rows[0] and return res.sendStatus(404) (or a JSON error) when it's missing. | server/routes/orders.js:10 |
A cancel endpoint in the orders router, written the way the rest of the file is, plus the error handling it currently lacks:
router.post('/:id/cancel', async (req, res, next) => {
try {
const { rows } = await db.query('update orders set status = $2 where id = $1 returning id', [req.params.id, 'cancelled']);
if (!rows[0]) return res.status(404).json({ error: 'no such order' });
res.json({ id: rows[0].id, status: 'cancelled' });
} catch (err) {
next(err); // Express 4 won't do this for you
}
});And its test, in the style of test/orders.test.js:
test('cancel needs a real order', async () => {
await request(app).post('/api/orders/999/cancel').expect(404);
});The status column is illustrative: the repo has no schema, so check the real table first. The test needs a database, unlike the existing empty-cart test.
Collapsed
Shopfront allows any Express 4.x from 4.19.2, and with no lockfile a fresh install gets the newest 4.x, so nobody can tell from the repo which version a given environment runs. Express 5 is the current major. Checked 2026-10-13. Sources: package.json:10 (no lockfile committed).
| Line | In Shopfront | Latest | What changed that matters here |
|---|---|---|---|
| express 4 | ^4.19.2; a fresh install resolves to 4.22.3 | 4.22.3, 2026-09-14 (release) | Security fixes after 4.19.2: 4.20.0 CVE-2024-43796, XSS via res.redirect() (advisory); 4.21.1 a cookie fix; 4.22.3 CVE-2026-4867 in path matching. Shopfront doesn't redirect, but it should still be on ≥ 4.22.3. 4.20.0 also changed some defaults (urlencoded depth, redirect body), none used here (4.x history). |
| express 5 | — | 5.3.0, 2026-10-09 (release) | Node 18+. Rejected promises go to next automatically, which would fix Shopfront's crash-on-query-error. Stricter path syntax (named wildcards, no regex characters in strings), req.query read-only, several old methods removed (Migrating to 5). |
1. Commit a lockfile so the version is reproducible. 2. Move to the latest 4.x. 3. Read the migration guide: Shopfront's routes use plain :id paths, so the path-syntax changes shouldn't bite. 4. Try the official codemod, then run the tests:
npx codemod@latest @expressjs/v5-migration-recipe
npm install express@5 && npm testEverywhere else, this guide describes Express 4, the version Shopfront runs.
Collapsed
Where to go next, in reading order. Express links are the 4.x pages (the unversioned ones now show v5). Links checked 2026-10-13.
| Resource | What it covers | Checked |
|---|---|---|
| Express 4 guides | ||
| Routing | Route paths and parameters, routers mounted at a prefix. | 2026-10-13 |
| Using middleware | The stack, order, next(), router-level middleware. | 2026-10-13 |
| Error handling | Error middleware, async handlers in v4, the default handler. | 2026-10-13 |
| Reference | ||
| Express 4.x API | app, req, res, express.json. | 2026-10-13 |
| body-parser 1.x | JSON parser options and error types (400/413/415). | 2026-10-13 |
| supertest | Testing an exported app. | 2026-10-13 |
| MDN: Using Fetch | Calling the API from the browser. | 2026-10-13 |
| Versions & upgrading | ||
| Migrating to Express 5 | Breaking changes and the codemod. | 2026-10-13 |
| Releases | Release notes for every version. | 2026-10-13 |
| Security advisories | CVEs and patched versions. | 2026-10-13 |
Collapsed
Common tasks and the command for each, from package.json.
| I want to… | Run |
|---|---|
| Run API and client | npm run dev |
| Build the client | npm run build |
| Run tests | npm test |
| Lint | npm run lint |
npm run dev # API + client, with hot reload
npm test # Jest + Supertest
npm run lint # ESLint
npm run build # production client bundleCollapsed
Across the guide, the first mention of a term in each section is underlined: hover it for the definition, or click to jump here.
| Term | Meaning (as used in Shopfront) | Where it shows up (repo path) |
|---|---|---|
| Domain | ||
| Shopper | A customer browsing the storefront and placing orders. | client/src/pages/ |
| Staff | Acme employees who manage the catalog; identified by the x-staff-role header. | server/auth.js |
| Cart | The items array posted to create an order. Must be non-empty. | server/routes/orders.js |
| Code & config | ||
| requireStaff | Express middleware that returns 403 unless the staff header is present. Guards /api/admin. | server/auth.js |
| createOrder | Handler for POST /api/orders: validates the cart, inserts the row, returns the id. | server/routes/orders.js |
| newAdmin / VITE_FLAG_NEW_ADMIN | Feature flag that adds the /admin route. Set with VITE_FLAG_NEW_ADMIN at build time. | client/src/flags.js |
| DATABASE_URL | PostgreSQL connection string read by the pool. | server/db.js |
| SSO proxy / SSO (single sign-on) | Company proxy in front of the app that, per a code comment, identifies staff. | server/auth.js |
| React Router / createBrowserRouter | The client-side routing library: createBrowserRouter maps URLs to page components. Not the same as an Express router. | client/src/routes.jsx |
| Vite | The client's dev server (port 5173) and production bundler. It inlines VITE_* variables at build time, which is why /admin only exists in builds made with the flag on. | package.json, client/src/flags.js |
| Express | ||
| Express | The Node.js web framework Shopfront's API is built on: one app, routers per resource, and middleware. See the Express deep dive. | server/index.js |
| app.use | Registers middleware or mounts a router at a path prefix; matches any HTTP method, and runs in the order registered. | server/index.js:7-10 |
| Middleware | A (req, res, next) function in the request pipeline that responds or passes the request on. | server/index.js:7-10 |
| Router (as in express.Router) | A group of routes mounted at a path prefix. | server/routes/ |
| express.json | Middleware that parses JSON request bodies into req.body. | server/index.js:7 |
| Error-handling middleware / Error middleware | A four-argument (err, req, res, next) function that turns errors into responses. Not used in Shopfront. | docs |
| Unhandled rejection | A rejected promise nobody catches; on current Node versions it crashes the process. | docs |
| supertest | Test library that sends HTTP requests to an Express app in-process. | test/orders.test.js |
| fetch | The browser API for HTTP requests; check response.ok for errors. | MDN |
| pg / node-postgres / Pool | The Postgres client; one shared pool from DATABASE_URL, used through db.query with $1 placeholders. | server/db.js |
| PostgreSQL | ||
| PostgreSQL / Postgres | The relational database that stores Shopfront's products and orders. See the PostgreSQL guide. | server/db.js |
| Parameterized query / Parameterized SQL | SQL with $1, $2… placeholders and a separate values array, so values are never read as SQL. Every Shopfront query is one. | server/routes/orders.js:6 |
| RETURNING | SQL clause that makes an INSERT or UPDATE send back columns of the rows it changed, such as a generated id. | server/routes/orders.js:6 |