12 KiB
P2P Survey App
A serverless peer-to-peer survey application built with Ionic and Angular. Survey creators store all data locally in their browser and participants connect directly via WebRTC — no backend server required.
Table of Contents
- Overview
- Architecture
- Features
- Setup & Installation
- How It Works
- Question Types
- Technology Stack
- Limitations
- Self-Hosting the PeerJS Signaling Server
- Development Commands
- Technology Choices
Overview
P2P Survey App lets you create and distribute surveys without any server-side infrastructure. All survey data — questions, participant tokens, and responses — lives exclusively in the survey creator's browser (IndexedDB). Participants connect directly to the creator's browser using WebRTC peer-to-peer data channels, facilitated by the PeerJS library.
Architecture
Creator Browser (Host)
┌─────────────────────────────────────────────┐
│ Ionic/Angular App │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ PeerService │ │ Dexie (IndexedDB)│ │
│ │ (PeerJS/WebRTC)│<─>│ surveys │ │
│ └────────┬────────┘ │ participants │ │
│ │ │ responses │ │
│ Peer ID: survey-{id}└──────────────────┘ │
└───────────┼─────────────────────────────────┘
│
[PeerJS Cloud – signaling only, not data relay]
│
Participant Browser
┌─────────────────────────────────────────────┐
│ Opens: /participate?host=survey-{id} │
│ &token={uuid} │
│ │
│ 1. Connect to host peer via WebRTC │
│ 2. Send { type: 'join', token } │
│ 3. Receive survey questions │
│ 4. Fill in answers │
│ 5. Send { type: 'submit', answers } │
└─────────────────────────────────────────────┘
Star topology
The survey creator acts as the central hub. Each participant opens a direct WebRTC data channel to the creator's browser. The creator's browser validates tokens, stores responses, and optionally pushes aggregated results back.
The PeerJS signaling server is only used for the initial WebRTC handshake (exchanging ICE candidates). Once connected, all data flows directly between the two browsers — the signaling server never sees response data.
Features
- Create surveys with four question types: free text, multiple choice, yes/no, and 1–5 rating
- Generate unique participant links — each link contains a UUID token that identifies one participant
- Token-based access control — each token can only be submitted once; reuse is rejected by the host
- Draft saving — participants can save answers before final submission without locking their token
- Live results — the creator sees responses update in real time using
Dexie's
liveQuery - Optional results sharing — the creator can toggle whether participants see aggregated results after submitting
- CSV export — download all responses as a comma-separated file
- Local-only storage — all data lives in the creator's IndexedDB; delete the survey and the data is gone
Setup & Installation
Prerequisites
- Node.js 18+ and npm 9+
- A modern browser (Chrome, Firefox, Edge, Safari)
Install
# 1. Install Ionic CLI globally (skip if already installed)
npm install -g @ionic/cli
# 2. Install project dependencies
npm install
# 3. Start the development server
ionic serve
The app opens at http://localhost:8100.
Build for production
ionic build --prod
Output is written to www/. Deploy the contents of www/ to any static
web host (GitHub Pages, Netlify, Vercel, Nginx, etc.).
How It Works
Creator Flow
- Open the app and click Create Survey.
- Enter a title (required) and optional description.
- Add questions — choose a type for each and mark which are required.
- Click Create to save the survey to IndexedDB and navigate to the Survey Detail page.
- On the detail page:
- Toggle Show results to participants if desired.
- Enter a number and click Generate Links to create unique participant tokens. Copy links to share.
- Click Start Hosting to initialise the PeerJS peer. The app listens
for incoming connections using the peer ID
survey-{surveyId}.
- Keep this page or the Results page open while collecting responses. If the creator navigates away, the peer is destroyed and participants cannot connect.
- Click View Results to see live aggregated and individual responses.
- Click Export CSV to download all responses.
Participant Flow
- Open the unique link shared by the survey creator.
- The app parses
hostandtokenfrom the URL query parameters. - A WebRTC connection is established to the host's peer ID.
- The participant sends
{ type: 'join', token }. - The host validates the token:
- Invalid token → error card is shown.
- Already submitted → error card is shown (token is locked).
- Valid → survey questions are sent back.
- Participant fills in answers. Answers are auto-saved as drafts after each blur event (free text) or selection change (other types).
- Click Submit to finalise. The token is locked on the host side.
- If the creator enabled result sharing, aggregated results are shown.
Question Types
| Type | Input widget | Aggregation |
|---|---|---|
| Free Text | Multi-line textarea | List of all answers |
| Multiple Choice | Radio buttons (creator-defined options) | Count per option + bar chart |
| Yes / No | Radio buttons (Yes / No) | Count per option + bar chart |
| Rating | 5 buttons (1–5) | Average score + distribution bar chart |
Technology Stack
| Library | Version | Role |
|---|---|---|
@ionic/angular |
8.x | Mobile-first UI components |
@angular/core |
20.x | Application framework |
peerjs |
1.5.x | WebRTC data channel wrapper |
dexie |
4.x | IndexedDB wrapper with liveQuery |
qrcode |
1.5.x | QR code generation (optional usage) |
Limitations
Host must be online
The survey creator's browser must be open and on the Survey Detail or Results page for participants to submit responses. There is no server that relays or buffers messages. If the creator goes offline, participants see a "Host is Offline" card.
Data loss
All data is stored in the creator's browser IndexedDB. Data is lost if:
- The creator explicitly deletes the survey
- The browser's storage is cleared (private/incognito mode, clearing site data)
- The browser's IndexedDB quota is exceeded
Export responses to CSV regularly to prevent data loss.
PeerJS Cloud rate limits
The free PeerJS Cloud signaling server (0.peerjs.com) has rate limits and
may occasionally be unavailable. For production use, self-host the signaling
server (see below).
NAT traversal
WebRTC uses STUN to traverse most NAT configurations. Strict corporate
firewalls or symmetric NAT may block direct connections. For full reliability
in such environments, add a TURN server to the PeerJS configuration in
src/app/services/peer.service.ts.
Browser compatibility
Requires a browser that supports:
- WebRTC (DataChannels)
- IndexedDB
crypto.randomUUID()
All modern browsers (Chrome 86+, Firefox 78+, Safari 15.4+, Edge 86+) satisfy these requirements.
Self-Hosting the PeerJS Signaling Server
For production deployments, run your own PeerJS server to avoid rate limits.
Option 1: npm
npm install -g peer
peerjs --port 9000 --key peerjs
Option 2: Docker
docker run -p 9000:9000 peerjs/peerjs-server
Option 3: Node.js
// server.js
const { PeerServer } = require('peer');
const server = PeerServer({ port: 9000, path: '/peerjs' });
Configure the app to use your server
Edit src/app/services/peer.service.ts and update the Peer constructor:
this.peer = new Peer(peerId, {
host: 'your-peer-server.example.com',
port: 9000,
path: '/peerjs',
secure: true, // use wss:// if your server has TLS
});
Development Commands
# Start dev server with live reload
ionic serve
# Build for production
ionic build --prod
# Run TypeScript type check
npx tsc --noEmit
# Lint
npx ng lint
# Build Angular app only (no Ionic wrapper)
npx ng build
Technology Choices
PeerJS over Gun.js
Gun.js is a decentralized graph database that syncs between peers. However, a survey has a star topology (one host, many participants) — not a mesh where every peer is equal. PeerJS maps directly to this model: the creator opens one peer ID, participants connect to it, and all data flows through that central point. Token validation and response deduplication are trivial to enforce at the host. Gun.js would require additional complexity to achieve the same guarantees.
Dexie.js over raw IndexedDB
Raw IndexedDB uses a callback-based API that is verbose and error-prone.
Dexie wraps it in clean Promises, adds a TypeScript-friendly schema
definition, and provides liveQuery — a reactive subscription mechanism
that automatically re-runs queries when the underlying data changes. This
powers the live results view without any manual polling or event wiring.
Module-based Angular over Standalone Components
The Ionic CLI scaffolds a module-based project by default with Angular 20.
Module-based components provide a clear separation of concern via NgModule
declarations and are well-supported by the Ionic ecosystem.
Project Structure
src/
└── app/
├── shared/
│ └── models/
│ └── survey.models.ts # All TypeScript interfaces + P2PMessage type
├── database/
│ └── database.ts # Dexie singleton (AppDatabase)
├── services/
│ ├── peer.service.ts # PeerJS wrapper (NgZone-aware)
│ ├── survey.service.ts # Survey/participant CRUD
│ └── response.service.ts # Response storage + aggregation
├── pages/
│ ├── home/ # Survey list
│ ├── create-survey/ # Survey creation/editing
│ ├── survey-detail/ # Settings, link generation, hosting
│ ├── survey-results/ # Live results view
│ └── participate/ # Participant survey form
├── app-routing.module.ts
├── app.component.ts
└── app.module.ts