REST API & HTTP
Background reading: how the browser and server communicate
This page explains the design pattern behind Montage Concord's web interface from first principles. It is aimed at someone with no prior web development experience.
The Basic Idea: Client and Server
When you run python -m concord_server.server, Python starts a server
— a program that sits and waits for requests. When you open a browser and go to
http://localhost:8000, your browser is the client — it sends requests
and displays the responses.
Think of it like a restaurant. The kitchen (server) has the data and can do computation. The waiter system (HTTP) carries orders (requests) from the table (browser) to the kitchen, and brings back dishes (responses).
HTTP: The Language of the Web
HTTP (HyperText Transfer Protocol) is the standard format for messages between clients and servers. Every HTTP interaction is a request from the client followed by a response from the server.
A request has three main parts:
- Method — what kind of action: GET, POST, PUT, DELETE, etc.
- URL (path) — what resource:
/api/timeseries - Body (optional) — extra data sent with the request, usually JSON
A response has two main parts:
- Status code — a number indicating success or failure: 200 (OK), 400 (bad request), 404 (not found), 500 (server error)
- Body — the actual data returned, usually JSON
GET vs POST
GET and POST are the two HTTP methods used in Montage Concord. They have different purposes:
| Method | Purpose | Has body? | Analogy |
|---|---|---|---|
| GET | Retrieve data. Should not change anything on the server. | No (params go in URL) | "Give me the current PSD" |
| POST | Send data to trigger an action or create/change state. | Yes (JSON body) | "Load this file" / "Switch to bipolar" |
GET example — fetching time series
The browser asks for data with parameters in the URL:
GET /api/timeseries?t_start=0&t_end=10&channels=SEEG1,SEEG2
The ? starts the query string. Parameters are key=value pairs separated by &.
The server responds with 200 OK and a JSON body like:
{
"channels": ["SEEG1", "SEEG2"],
"times": [0.0, 0.0005, ...],
"values": [[...], [...]],
"events": []
}
POST example — loading a file
The browser sends data in the request body:
POST /api/load
Content-Type: application/json
{"path": "/home/user/data/sub-HUP117_run-01_ieeg.edf"}
The server loads the file, updates its internal state, and responds with metadata about what was loaded.
JSON: The Data Format
JSON (JavaScript Object Notation) is the standard format for sending structured data over HTTP. It looks like Python dictionaries and lists:
// JSON uses double quotes (not single), true/false/null (not True/False/None)
{
"name": "SEEG1",
"status": "good",
"coordinates": [12.3, -4.1, 8.5],
"active": true,
"notes": null
}
Python's json module converts between Python dicts/lists and JSON strings.
FastAPI handles this conversion automatically — Python dicts returned from route functions
are automatically serialized to JSON.
The Full Request-Response Cycle
Here is what happens step by step when the browser fetches the time series:
// In the browser (JavaScript):
const response = await fetch("/api/timeseries?t_start=0&t_end=10");
const data = await response.json();
// data is now a JavaScript object: { channels: [...], times: [...], ... }
renderTimeSeries(div, data);
# On the server (Python):
@app.get("/api/timeseries")
def api_timeseries(t_start: float = None, t_end: float = None, ...):
recording = require_recording() # get current recording from state
result = get_timeseries(recording, ...) # call viz function
return result # FastAPI converts dict → JSON
localhost and Ports
localhost means "this same computer" — as opposed to a remote server on the internet.
The number after the colon (:8000) is the port — a way to distinguish
multiple servers on the same machine. Port 8000 is a common convention for development web servers.
When you type http://localhost:8000, the browser connects to the Python server
running on your own machine. This is why the server must be running before you open the browser.
Static vs Dynamic Content
The server serves two kinds of content:
| Type | What it is | How it works |
|---|---|---|
| Static | HTML, CSS, JavaScript files | Sent as-is from the static/ directory. These never change while the server is running. |
| Dynamic | API responses (/api/* routes) |
Computed on demand from the current Recording state. Changes when the user loads a file or changes settings. |
When you open http://localhost:8000, the browser receives index.html (static).
That HTML file contains <script> tags that load the JavaScript modules (also static).
The JavaScript then makes dynamic API calls to /api/* routes to fetch data.
How Montage Concord Uses This Pattern
Putting it all together:
- You start the Python server. It loads no data yet — state is empty.
- You open
http://localhost:8000. Browser receives the HTML + JS files (static). - You type an EDF path and click Load. Browser sends
POST /api/load. - Server calls
read_bids_ieeg(), stores Recording inAppState. Returns channel metadata. - Browser receives channel list, builds sidebar. Then sends
GET /api/timeseries. - Server calls
get_timeseries(recording, ...). Returns JSON with times + values. - Browser renders Plotly chart. You can now interact with the data.
- You zoom in. Browser sends a new
GET /api/timeserieswith newt_start/t_end. Server responds with higher-resolution data for that window.
The key insight: the server holds all the data in memory; the browser only holds what's needed for the current view. This is why the browser stays fast even with large EDF files.