Skip to content

Files API

CRUD over project files. Source: services/api/src/routes/project-files.ts and services/api/src/routes/project-files/.

The filesystem is the source of truth (PROJECTS_ROOT/<projectId>/). The project_files table is a fast-read cache kept in sync by the API.

List

GET /projects/:projectId/files

Returns a flat list:

[
  { path: "package.json", size: 423, updatedAt: "..." },
  { path: "src/App.tsx",  size: 1820, updatedAt: "..." },
  ...
]

Add ?tree=1 for a nested tree response (for the file explorer).

Read

GET /projects/:projectId/files/<path>

Returns the raw file body with Content-Type guessed from the extension. Binary files are streamed as application/octet-stream.

Create / replace

PUT /projects/:projectId/files/<path>
Content-Type: text/plain
<body>

Creates parent directories as needed. The path is always validated to stay within the project root; ../ traversal returns 400 path_traversal.

After write:

  1. The file is staged in the project's Git repo.
  2. The project_files cache is updated.
  3. The WS service rebroadcasts the new content into the collaboration room.

Delete

DELETE /projects/:projectId/files/<path>

Bulk operations

POST /projects/:projectId/files/bulk
{
  "operations": [
    { "op": "write",  "path": "src/a.ts", "content": "..." },
    { "op": "rename", "from": "src/b.ts", "to": "src/c.ts" },
    { "op": "delete", "path": "old.ts" }
  ]
}

Atomic per file; the whole batch is committed once.

GET /projects/:projectId/files/search?q=<regex>&maxResults=50

Server-side grep confined to the project root.

Live preview integration

When a file is written, the project's Vite dev server picks up the change via filesystem watching, then HMR, then an instant browser update. The frontend iframe listens for HMR events and surfaces build errors in a banner.

Permissions

Identical to the parent project: read-only for visibility = public, full access for workspace members.

See also