Backend API for the Gas Leak Detector system.
Handles ESP8266 sensor ingestion, persists data to Supabase,
and streams real-time updates to the Android app via WebSocket.
Part of the Gas Leak Detector ecosystem:
Server •
ESP8266 Firmware •
Android App
⚠️ This repository contains the server component only.
No hosted instance or pre-built binaries are provided - deployment is your responsibility.
api/
historical.js GET - paginated historical sensor data
ingest.js POST - primary ESP8266 data ingestion endpoint
logs.js GET - recent logs with pagination
realtime-config.js GET - Supabase credentials for Android WebSocket
stats.js GET - hourly aggregated statistics per device
status.js GET - latest reading for a device
lib/
email.js Resend email alert helper
supabase.js Supabase client, queries, and realtime subscriptions
validator.js API key validation, PPM status calculation, input validation
supabase/
schema.sql Full database schema - run once in Supabase SQL Editor
All endpoints require the header x-api-key: <VALID_API_KEY>.
Primary endpoint for the ESP8266 to push sensor readings. Accepts a single reading or a batch of up to 20.
Single request body:
{ "device_id": "ESP_GASLEAK_01", "ppm": 245 }Batch request body:
{ "batch": [
{ "device_id": "ESP_GASLEAK_01", "ppm": 245 },
{ "device_id": "ESP_GASLEAK_01", "ppm": 260 }
]}Response (single):
{ "success": true, "log_id": 1042, "status": "normal" }Response (batch):
{ "success": true, "count": 2, "results": [{ "id": 1042, "status": "normal" }, ...] }On each ingested reading, if the status is danger and the cooldown window has passed, an email alert is dispatched via Resend.
Returns raw sensor readings for a time range with cursor-based pagination. The Android app calls this on startup to populate the chart.
Query parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
device_id |
No | - | Filter by device. Omit to return all devices. |
range |
No | 1d |
Time window: 1h, 6h, 1d, 7d, 30d |
cursor |
No | - | Last seen row id for pagination |
Response:
{
"data": [{ "id": 1001, "gas_ppm": 245, "status": "normal", "created_at": "..." }, ...],
"total": 1000,
"nextCursor": 2001,
"range": "1d",
"device_id": "ESP_GASLEAK_01"
}Returns up to 1000 rows per page. When nextCursor is null, all data has been returned. The response is gzip-compressed if the client sends Accept-Encoding: gzip.
Returns recent readings with descending order and cursor pagination. Intended for debugging and lightweight dashboard queries.
Query parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
device_id |
No | - | Filter by device |
limit |
No | 100 |
Rows per page (max 500) |
cursor |
No | - | Last seen row id for pagination |
Response:
{ "logs": [...], "total": 100, "nextCursor": 940 }Returns the most recent reading for a specific device. Useful for a quick health check or polling fallback.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
device_id |
Yes | Target device |
Response:
{
"id": 1042,
"device_id": "ESP_GASLEAK_01",
"gas_ppm": 245,
"status": "normal",
"ip_address": "192.168.1.5",
"created_at": "2026-03-15T10:00:00+00:00"
}Returns hourly aggregated statistics from gas_logs_hour. This is the endpoint the Android statistics chart reads from - it never touches raw data, so queries stay fast regardless of data volume.
Query parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
device_id |
No | - | Filter by device. Omit to return all devices. |
limit |
No | 10 |
Number of hourly buckets to return (max 50) |
Response:
{
"data": [
{
"bucket": "2026-03-21T10:00:00+00:00",
"avg_gas": 213.45,
"min_gas": 180.0,
"max_gas": 310.0,
"sample_count": 1800
}
]
}Results are ordered by bucket descending - most recent hour first.
Provides the Supabase URL and anonymous key required for the Android app to establish a direct WebSocket connection to Supabase Realtime. The primary purpose is to allow the Android client to dynamically obtain credentials for subscribing to real-time INSERT events on the gas_logs_raw table, eliminating the need to hardcode sensitive information in the APK.
Response:
{ "url": "https://xxx.supabase.co", "anonKey": "eyJ..." }The Android app calls this endpoint once at startup, builds the WebSocket URL using the returned credentials, and subscribes to INSERT events on gas_logs_raw to receive live sensor readings.
Run supabase/schema.sql once in the Supabase SQL Editor. It creates:
devices- registered device registrygas_logs_raw- raw readings from ESP, realtime-enabledgas_logs_minute- per-minute aggregatesgas_logs_hour- per-hour aggregatesaggregate_gas_minute()andaggregate_gas_hour()- aggregation functionspg_cronjobs for both aggregation functions- Row Level Security policies (anon read-only)
Raw rows in gas_logs_raw are never deleted automatically. Data is preserved based on status:
| Status | Retention |
|---|---|
normal |
Manual cleanup only - no automatic deletion |
warning |
Kept permanently |
danger |
Kept permanently |
Historical queries beyond recent data should use the
gas_logs_minuteandgas_logs_houraggregate tables for performance.
| Variable | Status | Description |
|---|---|---|
SUPABASE_URL |
Required | Your Supabase project URL |
SUPABASE_ANON_KEY |
Required | Supabase anonymous key (used by Android WebSocket) |
SUPABASE_SERVICE_KEY |
Required | Supabase service role key (used by all server-side writes) |
VALID_API_KEY |
Required | Shared secret sent in x-api-key header by ESP and app |
RESEND_API_KEY |
Optional | Resend API key for email alerts |
ALERT_EMAIL |
Required | Recipient address for danger-level alerts |
DANGER_THRESHOLD |
Required | PPM value at or above which status becomes danger (Recommended: 800 for MQ-6) |
WARNING_THRESHOLD |
Required | PPM value at or above which status becomes warning (Recommended: 300 for MQ-6) |
EMAIL_COOLDOWN_MINUTES |
Optional | Minimum minutes between repeated email alerts (default: 2) |
Copy .env to .env.local for local development.
Vercel is the recommended deployment target. The project deploys as serverless functions with zero configuration.
- Click the button above
- Fill in a repository name and click Create
- Add all environment variables in the Vercel dashboard under Settings > Environment Variables
- Click Deploy
- Install the Vercel CLI:
npm install -g vercel- Clone and enter the repository:
git clone https://github.com/gasleakdetector/gasleakdetector-server.git
cd gasleakdetector/gasleakdetector-server- Link to Vercel and deploy:
vercel- Add environment variables via the dashboard or CLI:
vercel env add SUPABASE_URL
vercel env add SUPABASE_ANON_KEY
# ... repeat for all variables- Deploy to production:
vercel --prodYour API will be available at https://<project-name>.vercel.app.
npm installCreate a .env file with the variables listed above, then:
vercel devThe API is available at http://localhost:3000.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Apache 2.0 © Gas Leak Detector
Have questions or ran into issues? Reach out at pan2512811@gmail.com.
Found this project useful? Consider giving it a ⭐ - it means a lot and helps others discover it. Thanks!
