| config | ||
| data | ||
| public | ||
| scripts | ||
| test | ||
| .dockerignore | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package.json | ||
| README.md | ||
| server.mjs | ||
Personal Survey App
A small self-hosted survey app for a personal project. It serves a one-question-at-a-time survey in random order, collects configured personal fields, and stores responses as local files.
Run Locally
npm start
Open http://localhost:3000.
No install step is needed because the app uses only Node built-ins. Use Node 20 or newer.
Local runs bind to 127.0.0.1 by default. In Docker, HOST=0.0.0.0 is set so nginx can reach the container.
Change Questions
Edit config/questions.json. The server reads this file whenever the browser loads the survey, so simple content changes do not require a rebuild.
Use the optional top-level instructions array to show concise guidance above the personal details fields. Each entry is rendered as a paragraph, for example:
"instructions": [
"This questionnaire measures broad personality traits.",
"Answer based on how you **generally are**; there are no right or wrong answers."
]
Text enclosed in **double asterisks** is shown in bold.
Supported question types:
texttextareanumbersingle_choicescaleyes_no
Use stable id values. The CSV export uses those ids as column names, and the JSONL file stores the question label used when each response was submitted.
Supported personal field types:
texttextareaemailnumbermulti_choice
multi_choice renders a list of toggle options and stores the selected option values as an array. The included configs use it for activities, asking respondents to select any activities they are involved in. Edit that field's options list in the relevant config file to change the activity choices.
Before final submission, respondents must confirm that they answered all questions to the best of their ability and answer whether they have already completed the survey. These are stored as completionChecks.bestEffortConfirmed and completionChecks.alreadyCompletedSurvey in JSONL, and as best_effort_confirmed and already_completed_survey in CSV exports.
Big Five Inventory
This repo includes a ready-to-use public-domain IPIP Big Five config:
QUESTIONS_FILE=config/questions.big-five-ipip-50.json npm start
That config uses the 50-item IPIP representation of Goldberg's Big-Five factor markers:
- Source: https://www.ipip.ori.org/New_IPIP-50-item-scale.htm
- IPIP public-domain statement: https://www.ipip.ori.org/
- IPIP citation guidance: https://www.ipip.ori.org/newCitation.htm
For deployment, either copy config/questions.big-five-ipip-50.json over config/questions.json, or set QUESTIONS_FILE to the Big Five config path in Docker.
The Big Five config stores scoring metadata for each item. Participants only see the item text and the 1-5 scale.
Big Five Inventory-2
This repo also includes the 60-item Big Five Inventory-2 (BFI-2) config:
QUESTIONS_FILE=config/questions.big-five-bfi-2.json npm start
For Docker, set QUESTIONS_FILE=/app/config/questions.big-five-bfi-2.json.
Then generate BFI-2 domain and facet scores:
npm run score:bfi2 -- data/bfi-2-scores.csv
With Docker Compose:
docker compose exec survey npm run score:bfi2 -- /app/data/bfi-2-scores.csv
Because ./data is mounted to /app/data, the generated file appears at data/bfi-2-scores.csv on the host.
The score file includes the five BFI-2 domains:
- Extraversion
- Agreeableness
- Conscientiousness
- Negative Emotionality
- Open-Mindedness
It also includes 15 facet scores. Domains and facets are scored as means across answered items after reverse-keying.
BFI-2 items are copyright 2015 by Oliver P. John and Christopher J. Soto. The Berkeley Personality Lab says the BFI-2 is not public domain, but is freely available for non-commercial research use. Commercial use requires contacting the authors.
HEXACO Inventory
This repo also includes a public-domain IPIP-HEXACO config:
QUESTIONS_FILE=config/questions.hexaco-ipip-240.json npm start
Then generate HEXACO domain and facet scores:
npm run score:hexaco -- data/hexaco-scores.csv
The score file includes the six HEXACO domains:
- Honesty-Humility
- Emotionality
- Extraversion
- Agreeableness
- Conscientiousness
- Openness to Experience
It also includes 24 facet scores. Domains and facets are scored as means across answered items after reverse-keying.
This is the IPIP representation of HEXACO-PI constructs, not the official HEXACO-PI-R form:
- IPIP-HEXACO item/scoring source: https://ipip.ori.org/newHEXACO_PI_key.htm
- IPIP public-domain statement: https://www.ipip.ori.org/
- IPIP-HEXACO article: Ashton, Lee, & Goldberg (2007), https://doi.org/10.1016/j.paid.2006.10.027
If you want the official HEXACO-PI-R 60-item or 100-item form instead, download it from https://hexaco.org/hexaco-inventory and follow that site's usage rules. As of April 27, 2026, the official site says the downloadable forms are free only for non-profit academic research, public online survey apps must be password-protected or not searchable, and non-academic use requires contacting the authors.
Data Files
Submissions are saved in data/responses.jsonl. A spreadsheet-friendly CSV is regenerated at data/responses.csv after each submission.
For supported scored configs, score CSV files are also regenerated after each submission:
data/big-five-scores.csvforipip-big-five-50data/bfi-2-scores.csvforbfi-2data/hexaco-scores.csvforipip-hexaco-240
You can also regenerate the CSV at any time:
npm run export -- data/responses.csv
The JSONL file is the safest long-term source because it preserves the question order, question labels, survey version, and raw values for each response.
For the included IPIP-50 Big Five config, generate trait scores:
npm run score:big-five -- data/big-five-scores.csv
The scoring file includes means and sums for Extraversion, Agreeableness, Conscientiousness, Emotional Stability, and Intellect/Imagination. It also includes neuroticism_mean, calculated as the inverse of Emotional Stability.
Scoring scripts only include responses whose surveyId matches the selected config. Set INCLUDE_ALL_RESPONSES=1 if you deliberately want to score every row in responses.jsonl.
This is a survey tool, not a diagnostic or clinical assessment tool.
Optional CSV Export Endpoint
The app does not expose responses by default. To enable an authenticated CSV endpoint, set EXPORT_TOKEN.
EXPORT_TOKEN="change-this-long-random-token" npm start
curl -H "Authorization: Bearer change-this-long-random-token" \
http://localhost:3000/api/export.csv > responses.csv
Docker
Build and run directly:
docker build -t personal-survey-app .
docker run -d \
--name survey \
--restart unless-stopped \
-p 3000:3000 \
-e EXPORT_TOKEN="change-this-long-random-token" \
-v "$PWD/data:/app/data" \
-v "$PWD/config/questions.json:/app/config/questions.json:ro" \
personal-survey-app
Or adapt docker-compose.yml if your nginx container already shares an external Docker network.
Nginx Reverse Proxy
For a subdomain or dedicated host on the same Docker network:
server {
server_name survey.example.com;
location / {
proxy_pass http://survey:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Put TLS on nginx. The app stores personal data, so keep data/ backed up and do not serve it as a static directory.
Test
npm test