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).

Why run a server at all? The Python code that reads EDF files, applies filters, and computes metrics cannot run directly in the browser — browsers only run JavaScript. The server acts as a bridge: JavaScript in the browser asks Python on the server to do the heavy work and return results.

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:

  1. Method — what kind of action: GET, POST, PUT, DELETE, etc.
  2. URL (path) — what resource: /api/timeseries
  3. Body (optional) — extra data sent with the request, usually JSON

A response has two main parts:

  1. Status code — a number indicating success or failure: 200 (OK), 400 (bad request), 404 (not found), 500 (server error)
  2. 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:

MethodPurposeHas 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:

TypeWhat it isHow 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:

  1. You start the Python server. It loads no data yet — state is empty.
  2. You open http://localhost:8000. Browser receives the HTML + JS files (static).
  3. You type an EDF path and click Load. Browser sends POST /api/load.
  4. Server calls read_bids_ieeg(), stores Recording in AppState. Returns channel metadata.
  5. Browser receives channel list, builds sidebar. Then sends GET /api/timeseries.
  6. Server calls get_timeseries(recording, ...). Returns JSON with times + values.
  7. Browser renders Plotly chart. You can now interact with the data.
  8. You zoom in. Browser sends a new GET /api/timeseries with new t_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.