Skip to content

OpenHW Studio — Compiler Backend

The Express.js REST API server that compiles Arduino C++ sketches into .hex machine code using arduino-cli, and handles user authentication and data storage via MongoDB.


Table of Contents


Overview

The Compiler Backend is the central API server for OpenHW Studio. It:

  • Accepts Arduino C++ source code from the frontend
  • Invokes arduino-cli to compile it into an AVR .hex file
  • Returns the .hex payload to the frontend for simulation
  • Handles user authentication (login / registration) using JWT + bcrypt
  • Manages library installation via arduino-cli library commands
  • Connects to MongoDB for user and project data persistence
  • Receives and reviews custom component submissions from users

The server runs on http://localhost:5001.

Note on offline usage: The frontend now caches compiled .hex results in IndexedDB (browser-side). Once a sketch has been compiled at least once, subsequent runs with the same code bypass this server entirely and run from the local cache. See OFFLINE_AND_STORAGE.md for details.


Tech Stack

TechnologyPurpose
Node.js + ExpressWeb server and REST API
arduino-cliCompiling Arduino C++ to AVR .hex
MongooseMongoDB object modelling
JSON Web Tokens (JWT)Stateless authentication
bcryptjsPassword hashing
dotenvEnvironment variable management
corsCross-origin request handling
nodemonAuto-reload during development

Project Structure

openhw-studio-backend/
├── src/
│   ├── server.js                   # Entry point — Express app setup & startup
│   ├── controllers/
│   │   ├── compileController.js    # POST /api/compile — runs arduino-cli
│   │   ├── libController.js        # Library search & install via arduino-cli
│   │   ├── componentController.js  # Asset Registry & Approval pipeline
│   │   └── userController.js       # Admin & User profile management
│   ├── routes/
│   │   └── api.js                  # Route definitions
│   ├── models/                     # Mongoose data models (users, projects, etc.)
│   ├── db/                         # MongoDB connection setup
│   └── middleware/                 # Auth middleware (JWT verification)
├── temp/                           # Temporary .ino/.hex files (auto-cleaned)
├── env                             # Environment variables file (not committed)
├── nodemon.json                    # Nodemon config (ignores temp/)
├── package.json
└── .gitignore

API Endpoints

Compilation

MethodEndpointDescription
POST/api/compileCompile Arduino C++ code → returns .hex
GET/api/compile/pico/micropython-uf2Fetch cached default Pico MicroPython UF2 payload (CORS-safe via backend)

Request body:

json
{
  "code": "#include <Arduino.h>\nvoid setup() { ... }\nvoid loop() { ... }"
}

Response:

json
{
  "hex": ":100000000C945C000C9479000C94790..."
}

The frontend caches successful responses in IndexedDB (openhw-offline / hexCache). Repeated runs with unchanged code skip this endpoint entirely.

Library Management

MethodEndpointDescription
GET/api/lib-search?q=ServoSearch arduino-cli library index
POST/api/lib-installInstall a named library via arduino-cli
POST/api/lib-uninstallUninstall a named library
GET/api/lib-listList all installed libraries

Component Registry & Pipeline

MethodEndpointDescription
POST/api/components/submitUsers upload a custom component (ZIP content) for review
GET/api/admin/components/pendingList all submissions waiting for admin approval
POST/api/admin/components/approvePermanently merge a submission into the emulator library
DELETE/api/admin/components/reject/:submissionIdReject a specific submission by its unique ID
GET/api/admin/components/installedList all manually installed custom components
DELETE/api/admin/components/installed/:idRemove an installed component from the emulator
GET/api/admin/components/backupFull export of all installed components with source files

Component submissions made while offline are queued in the browser's IndexedDB and auto-submitted when connectivity is restored. The backend endpoint is unchanged.

Authentication

MethodEndpointDescription
POST/api/user/signupRegister a new user
POST/api/user/signinAuthenticate and receive JWT
POST/api/user/logoutInvalidate session
GET/api/user/profileGet current user profile details
PUT/api/user/profileUpdate user profile

Key Features

Compilation Pipeline (compileController.js)

  1. Receives raw C++ code via POST /api/compile
  2. Creates a uniquely named temporary .ino sketch file in temp/
  3. Executes arduino-cli compile targeting the arduino:avr:uno FQBN via Node's child_process.execFile
  4. Extracts the generated .hex content from the build output
  5. Sends the .hex string back to the frontend
  6. Cleans up the temporary directory recursively to prevent disk bloat

The frontend caches this response in IndexedDB. Subsequent Run clicks with the same code are served from the browser cache and never reach this endpoint.

Library Management (libController.js)

  • Wraps arduino-cli lib search and arduino-cli lib install as API endpoints
  • Allows the frontend's Library Manager UI to search and install Arduino libraries at runtime

Component Review Pipeline (componentController.js)

  • Unique submissionIds: Each upload gets a timestamped ID so rejecting one doesn't affect other pending versions.
  • Permanent Integration: Approval physically writes the ui.tsx, logic.ts, etc., to the emulator's component directory and updates its registry.
  • Atomic Rejection: One-click removal of specific submissions from the in-memory pending store.
  • Offline-Tolerant: The frontend queues failed submissions locally and retries automatically — no data is lost if the backend is temporarily unreachable.

Stability: nodemon + temp/ Ignore

The nodemon.json explicitly ignores the temp/ directory. Without this, file changes inside temp/ (created during active compilation) would cause nodemon to restart the server mid-compilation, resulting in ERR_CONNECTION_RESET errors on the frontend.

json
{
  "ignore": ["temp/"]
}

Setup & Running Locally

Prerequisites

  • Node.js 18+
  • npm 9+
  • MongoDB running locally (or a MongoDB Atlas connection string) for authentication and classroom features
  • arduino-cli installed and on your system PATH (or placed in the project root)
    • Download: https://arduino.github.io/arduino-cli/latest/installation/
    • After installing, run:
      bash
      arduino-cli config add board_manager.additional_urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
      arduino-cli core update-index
      arduino-cli core install arduino:avr
      arduino-cli core install rp2040:rp2040

If MongoDB is unavailable, the backend now starts in degraded mode so compile, library, examples, and component endpoints can still be used.

Installation

bash
cd openhw-studio-backend
npm install

Configure Environment

Create a file named .env in the project root (this file is gitignored):

env
PORT=5001
MONGO_URI=mongodb://localhost:27017/openhw-studio
JWT_SECRET=your_secret_key_here
SESSION_SECRET=your_session_secret_here
EXAMPLES_PATH=../path-to-openhw-studio-examples/examples
EMULATOR_PATH=../path-to-openhw-studio-emulator
FRONTEND_URL=http://localhost:5173

Start the Server

bash
# Development (auto-reload with nodemon)
npm run dev

# Production
npm start

Server will be available at http://localhost:5001


Environment Variables

The backend uses a .env (or env) file for configuration. Create one in the root directory based on the table below:

VariableDescriptionDefault
PORTPort the Express server listens on5001
MONGO_URIMongoDB connection string (local or Atlas)
JWT_SECRETSecret key for signing JSON Web Tokens
JWT_EXPIRES_INJWT expiration time (e.g., 7d)7d
SESSION_SECRETSecret key for Passport/express-session (required)
GOOGLE_CLIENT_IDGoogle OAuth 2.0 Client ID
GOOGLE_CLIENT_SECRETGoogle OAuth 2.0 Client Secret
GOOGLE_CALLBACK_URLAuthorised redirect URI for Google Loginhttp://localhost:5001/auth/google/callback
FRONTEND_URLFrontend URL for CORS configurationhttp://localhost:5173
FRONTEND_URLSComma-separated frontend origins for CORS allow-listhttp://localhost:5173,http://127.0.0.1:5173
EXAMPLES_PATHPath to the examples directory served at /examples (absolute or relative to the backend root)../openhw-studio-examples/examples
EMULATOR_PATHPath to the emulator repo root; the backend uses src/components inside it../openhw-studio-emulator
EMULATOR_COMPONENTS_PATHOptional direct path to the emulator component directory
PICO_MICROPYTHON_UART0_UF2_URLPreferred source (URL or local file path) for UART0-enabled Pico MicroPython UF2
PICO_MICROPYTHON_UF2_URLBackward-compatible alias for Pico MicroPython UF2 source (URL or local path)
PICO_MICROPYTHON_CACHE_TTL_MSCache lifetime (milliseconds) for backend-fetched Pico UF2 asset21600000

Notes:

  • Paths may be absolute or relative to the backend repository root.
  • Use EMULATOR_COMPONENTS_PATH if your emulator layout differs from the standard src/components structure.
  • For rp2040js MicroPython simulation, configure a UART0 REPL UF2 build. The official micropython.org Pico UF2 uses USB CDC REPL and is intentionally rejected.

Sample .env Setup:

env
PORT=5001
MONGO_URI=mongodb://localhost:27017/openhw-studio
JWT_SECRET=your_secret_key_here
JWT_EXPIRES_IN=7d
SESSION_SECRET=your_session_secret_here
GOOGLE_CLIENT_ID=your_id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your_secret_here
GOOGLE_CALLBACK_URL=http://localhost:5001/auth/google/callback
FRONTEND_URL=http://localhost:5173
EXAMPLES_PATH=../path-to-openhw-studio-examples/examples
EMULATOR_PATH=../path-to-openhw-studio-emulator

Released under the MIT License.