A simple survey web app.
Find a file
2026-05-20 20:14:43 +00:00
config Corrected spelling 2026-05-20 20:14:43 +00:00
data Simple web app 2026-05-12 19:28:58 +01:00
public Added instructions block 2026-05-16 09:32:39 +01:00
scripts Added final submission questions 2026-05-16 09:01:47 +01:00
test Added instructions block 2026-05-16 09:32:39 +01:00
.dockerignore Simple web app 2026-05-12 19:28:58 +01:00
.gitignore Simple web app 2026-05-12 19:28:58 +01:00
docker-compose.yml Added bfi-2 container 2026-05-20 20:12:21 +00:00
Dockerfile Simple web app 2026-05-12 19:28:58 +01:00
package.json Added BFI-2 2026-05-12 21:07:24 +01:00
README.md Added instructions block 2026-05-16 09:32:39 +01:00
server.mjs Added instructions block 2026-05-16 09:32:39 +01:00

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:

  • text
  • textarea
  • number
  • single_choice
  • scale
  • yes_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:

  • text
  • textarea
  • email
  • number
  • multi_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:

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:

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.csv for ipip-big-five-50
  • data/bfi-2-scores.csv for bfi-2
  • data/hexaco-scores.csv for ipip-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