GO திசைGoDisai

Story 5 · Hoppscotch

The Workbench Everyone Can Share

“An API collection that lives in one person's laptop is documentation with a single point of failure.”

15 min read · by Syed Jafer K

1. The Collection in the WhatsApp Group

Salna Express delivers parottas and salna across Chennai from eleven kitchens. Behind the app sits a small backend team of five, led by Priya.

The team tested its APIs with a desktop API client. Each person had their own copy of the collection. When someone added a new request, they exported the collection as a JSON file and dropped it into the team WhatsApp group with a message like "latest one, use this". By September there were fourteen files in the group called some version of salna-final.json, and nobody knew which one was the real latest.

Then Arun joined. On his first day he imported the newest-looking file, pointed it at the staging server, and spent the afternoon wondering why the order API kept returning 401. The collection he had was three weeks old. The login flow had changed, the token header had a new name, and the request that fetched the token had been fixed in someone else's copy only.

That same week, the security review at their payments partner asked a question that Priya could not answer well. "Where do your developers store the API keys for our sandbox?" The honest answer was "in environment files inside a third-party desktop client, synced to that company's cloud, on five laptops".

Priya talked it over with her friend Karthik, the one from chapter 3 who had stopped renewing certificates by hand.

"You want one workbench that the whole team opens in a browser," Karthik said. "Same collections, same environments, edits show up for everyone, and the data stays on a server you own. Look at Hoppscotch."


2. What Hoppscotch Is

Hoppscotch is an open-source API development tool. You build an HTTP request, send it, and read the response, the way you would with Postman or Insomnia. It started in 2019 under the name Postwoman, was created by Liyas Thomas, and is released under the MIT licence.

It runs in several shapes.

Shape What it is
Web app Runs in the browser, at hoppscotch.io or on your own server
Desktop app A native app for Windows, macOS and Linux that can point at your own server
CLI (hopp) Runs a collection and its tests from a terminal or a CI pipeline
Self-hosted Community Edition The web app, a backend and an admin dashboard that you run yourself

Besides plain REST requests, it speaks GraphQL, WebSocket, Server-Sent Events, Socket.IO and MQTT. It has collections and folders, environments with variables, pre-request scripts, tests written in JavaScript, and team workspaces where every member sees the same collections.

The self-hosted edition is the part this chapter is about. It keeps users, collections, environments and history in a PostgreSQL database that you control.


3. What the Self-Hosted Setup Looks Like

Three parts make up the self-hosted Community Edition.

  • The app is the Hoppscotch you use every day, served as a static web app.
  • The backend stores users, teams, collections and environments in PostgreSQL. The app talks to it over GraphQL and a WebSocket for live updates.
  • The admin dashboard is where you manage users, invite people, decide who can sign in, and set up sign-in providers.

You can run them as three containers, or as one "all-in-one" container that holds all three. Priya's team had one small VPS, so the all-in-one image was the obvious choice.

                 browser
                    |
               Caddy (HTTPS)
                    |
     hoppscotch all-in-one container
     /         |             \
   app      backend        admin
   :3000     :3170          :3100
                |
           PostgreSQL

By default the three parts listen on three ports. There is also a subpath mode, where everything is served on one port, the app at /, the admin dashboard at /admin and the backend at /backend. Subpath mode means one domain, one certificate and one line in the Caddy config, so that is what Priya used.


4. Before You Start

Priya wrote the requirements on a sticky note before she began.

  1. A Linux server with Docker and the Docker Compose plugin. Two CPU cores and 2 GB of memory is enough for a small team.
  2. A domain name. She used api-bench.salna.example, with its DNS record pointing at the server.
  3. Caddy in front, as in chapter 3, so the browser always talks HTTPS.
  4. A way to send email, if people will sign in with magic links. An SMTP account from any mail provider works.

She would sign the team in with Google, because every developer already had a company Google account. Email magic links would be the fallback.


5. The Compose File

She made a folder for it.

mkdir -p ~/hoppscotch && cd ~/hoppscotch

Then a docker-compose.yml with two services, the database and Hoppscotch itself.

services:
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: hoppscotch
      POSTGRES_PASSWORD: change-this-db-password
      POSTGRES_DB: hoppscotch
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U hoppscotch"]
      interval: 5s
      retries: 10

  hoppscotch:
    image: hoppscotch/hoppscotch:latest
    restart: unless-stopped
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "127.0.0.1:3080:80"

volumes:
  pgdata:

Two details matter here.

The port is bound to 127.0.0.1, so only Caddy on the same machine can reach it. Nobody on the internet can talk to the container directly and skip HTTPS.

The image tag says latest while she was trying things out. Once it worked, she replaced it with the exact version number she had tested, so that a restart could never pull a new release by surprise. Section 13 comes back to this.


6. The .env File

Hoppscotch reads its settings from environment variables. These are the ones Priya set, with the secrets replaced here.

# Database
DATABASE_URL=postgresql://hoppscotch:change-this-db-password@db:5432/hoppscotch

# Secrets (generate each one, never reuse)
JWT_SECRET=paste-a-long-random-string
SESSION_SECRET=paste-another-long-random-string
DATA_ENCRYPTION_KEY=exactly-32-characters-long-key!!

# Token lifetimes
TOKEN_SALT_COMPLEXITY=10
MAGIC_LINK_TOKEN_VALIDITY=3
REFRESH_TOKEN_VALIDITY=604800000
ACCESS_TOKEN_VALIDITY=86400000

# Where the app lives
ENABLE_SUBPATH_BASED_ACCESS=true
REDIRECT_URL=https://api-bench.salna.example
WHITELISTED_ORIGINS=https://api-bench.salna.example/backend,https://api-bench.salna.example,https://api-bench.salna.example/admin
VITE_BASE_URL=https://api-bench.salna.example
VITE_SHORTCODE_BASE_URL=https://api-bench.salna.example
VITE_ADMIN_URL=https://api-bench.salna.example/admin
VITE_BACKEND_GQL_URL=https://api-bench.salna.example/backend/graphql
VITE_BACKEND_WS_URL=wss://api-bench.salna.example/backend/graphql
VITE_BACKEND_API_URL=https://api-bench.salna.example/backend/v1

# Rate limiting
RATE_LIMIT_TTL=60
RATE_LIMIT_MAX=100

She generated the secrets on the server rather than inventing them.

openssl rand -hex 32     # for JWT_SECRET
openssl rand -hex 32     # for SESSION_SECRET
openssl rand -hex 16     # 32 hex characters, for DATA_ENCRYPTION_KEY

A few of these deserve a sentence each.

  • DATABASE_URL uses the host name db, which is the service name in the compose file. Inside Docker's network, the containers find each other by those names.
  • DATA_ENCRYPTION_KEY encrypts sensitive settings that Hoppscotch keeps in the database, such as the client secrets for sign-in providers. It must be exactly 32 characters. If you lose it, those stored secrets cannot be read and you will have to enter them again.
  • The VITE_ variables tell the web app where to find everything. They must use the public HTTPS address, because the person's browser is the one following them, not the server.
  • WHITELISTED_ORIGINS lists the addresses the backend accepts requests from. If the app's address is missing, sign-in fails with a CORS error in the browser console.

Then she locked the file down, because it now held every secret of the setup.

chmod 600 .env

7. Starting It and Creating the Tables

The first start brings up PostgreSQL with an empty database. Hoppscotch needs its tables created before it can work. That is done by running the database migrations once.

docker compose up -d db
docker compose run --rm --entrypoint sh hoppscotch -c "pnpx prisma migrate deploy"
docker compose up -d

The middle command starts a throwaway copy of the Hoppscotch container, runs the migration tool inside it against the database, and removes the container when it finishes. The last lines of its output listed each migration it had applied.

She checked that everything was up.

docker compose ps
docker compose logs -f hoppscotch

The logs settled after a few seconds. A quick request from the server itself returned the app's HTML.

curl -s http://127.0.0.1:3080 | head -5

Every upgrade needs this step too. New Hoppscotch releases often add tables or columns. After you pull a new image, run the same migration command before starting the new version, or the backend may fail with database errors.


8. Putting Caddy in Front

The Caddy part took three lines, exactly as Karthik had promised in chapter 3.

api-bench.salna.example {
    reverse_proxy 127.0.0.1:3080
}
sudo systemctl reload caddy

Caddy fetched a certificate, and https://api-bench.salna.example showed the Hoppscotch app. WebSocket traffic for live sync and the realtime tools goes through reverse_proxy without any extra settings.


9. The First Admin, and Who May Sign In

Priya opened https://api-bench.salna.example/admin. On a fresh install, the dashboard walks you through first-time setup, and the first person to sign in becomes the admin. That is why she did it the moment Caddy was ready, before telling anyone the address.

In the dashboard's settings she set up the sign-in methods.

  • Google. She created an OAuth client in the Google Cloud console with the redirect address https://api-bench.salna.example/backend/v1/auth/google/callback, and pasted the client ID and secret into the dashboard.
  • Email. She entered the SMTP address of the company mail account, so magic-link emails could be sent.

Then she turned off sign-in for everyone else. In the dashboard she could invite users by email, and she switched the instance so that only invited people could get in. A self-hosted tool that anyone on the internet can sign up to is not much safer than the one they were leaving.

She invited the four other developers. Each got an email, signed in with Google, and landed in an empty workspace.


10. Moving the Collections Across

The fourteen JSON files in WhatsApp were the next problem. Priya did not trust any of them, so she asked each person to export their own current collection, and she merged them by hand once, carefully, into a single file.

Hoppscotch imports collections from several formats, including Postman collections, OpenAPI specs, Insomnia exports and its own JSON. She used Import from the collections panel, chose the Postman format, and picked the merged file. Folders, requests, headers and bodies all came across.

Then she created a team workspace called Salna Backend and moved the collection into it. Personal workspaces belong to one person. A team workspace is shared, and every change shows up for every member as soon as it is saved.

Team members can have one of three roles.

Role Can do
Owner Everything, including adding and removing members
Editor Create, change and delete requests, folders and environments
Viewer Open and send requests, but not change the shared collection

Priya and one senior developer were owners. The rest were editors. The support engineer who sometimes checked order statuses was added as a viewer.

Then she deleted all fourteen JSON files from the WhatsApp group, and posted one link.


11. Environments and Secrets

The old collections had the staging address typed into every request. Change servers and you edited forty requests. Hoppscotch environments fix that with variables, written between double angle brackets.

GET <<baseUrl>>/v1/orders/<<orderId>>
Authorization: Bearer <<accessToken>>

She made three team environments, Local, Staging and Production-readonly, each with its own baseUrl. Switching the active environment in the top bar sent every request to a different server without changing a single request.

Some values should never be shared, such as the payment partner's sandbox key. Hoppscotch lets you mark a variable as a secret. A secret variable's value stays only in the browser of the person who entered it. It is not synced to the server or to teammates. Everyone sees that the variable exists, and each person fills in their own value.

That gave Priya her answer for the payments partner. Shared settings live on our own server. Keys live only on each developer's machine, entered by that developer.


12. Scripts That Remember the Token

Arun's first-day problem had been the login flow. Every request needed a fresh access token, and copying it by hand from the login response was how the stale-token mistakes happened.

Hoppscotch runs a small JavaScript test script after a response arrives. The login request got this one.

pw.test("login succeeds", () => {
  pw.expect(pw.response.status).toBe(200);
});

const body = pw.response.body;
pw.env.set("accessToken", body.access_token);

After every successful login, the new token was stored in the active environment, and every other request picked it up through <<accessToken>>.

A pre-request script runs before a request is sent. The order-creation request used one to make a unique reference every time, so repeated tests never clashed.

pw.env.set("orderRef", "TEST-" + Date.now());

Tests on the other requests checked what mattered to the business, not just the status code.

pw.test("order has a kitchen and a total", () => {
  const order = pw.response.body;
  pw.expect(order.kitchen_id).toBeType("number");
  pw.expect(order.total_paise).toBeType("number");
});

The results appear in a Test results tab next to the response, passed and failed, one line each.


13. When the Browser Will Not Send the Request

On day two, Arun tried to call the backend running on his own laptop at http://localhost:8000. Hoppscotch showed an error instead of a response.

This is the most common surprise with any browser-based API client. A web page cannot send arbitrary requests wherever it likes. Browsers enforce CORS, and they block a secure HTTPS page from calling a plain HTTP address. The request never left his browser.

Hoppscotch handles this with interceptors, which are other routes a request can take.

Interceptor How the request travels
Browser Straight from the page. Fine for public APIs that allow CORS.
Browser extension Through the Hoppscotch extension for Chrome or Firefox, which is not bound by the page's rules
Proxy Through a small proxy server that forwards the request
Agent Through the Hoppscotch Agent, a small program on your own machine. Best for localhost and private networks.

Arun installed the Agent, paired it with the web app once, and chose it as the interceptor in settings. His localhost requests went through.

Priya also self-hosted the proxy, called proxyscotch, for requests to staging servers that only the VPS could reach. She added it as another service in the compose file and pointed the Proxy interceptor's address at it in the app settings. A public proxy would have seen every request and token the team sent, which was exactly what she was trying to avoid.

Pin your versions. Once everything worked, she replaced hoppscotch/hoppscotch:latest with the exact tag she had tested. Upgrades became a planned step. Read the release notes, back up the database, pull the new tag, run the migrations, start it, and test a sign-in.


14. The Same Tests in CI

The scripts were useful while clicking. They were more useful if they ran on every pull request.

The Hoppscotch CLI runs a collection and its tests from the terminal.

npm install -g @hoppscotch/cli
hopp test salna-backend.json -e staging.json

It prints each request and each test with a pass or fail, and exits with an error code if anything fails, which is what a CI job needs. Priya exported the collection and a CI-only environment into the repository, and the pipeline ran hopp test against a staging deploy after every merge. The CI environment took the secret values from the pipeline's own secret store, never from the file.

Newer versions of the CLI can also fetch a collection straight from a self-hosted server using a personal access token, so the copy in the repository is not needed. She noted that for later and kept the simple version for now.


15. Backups, and the Bell

Everything the team had built now lived in one PostgreSQL database. Losing it meant losing every collection again. A nightly dump fixed that.

#!/usr/bin/env bash
# /home/priya/bin/hoppscotch-backup.sh
set -euo pipefail
cd /home/priya/hoppscotch
STAMP=$(date +%F)
docker compose exec -T db pg_dump -U hoppscotch hoppscotch | gzip > /backups/hoppscotch-$STAMP.sql.gz
find /backups -name 'hoppscotch-*.sql.gz' -mtime +14 -delete

The /backups folder was then copied off the server by the rclone job from chapter 4, encrypted on the way out. When the script failed, a curl to their ntfy topic from chapter 1 rang Priya's phone.

She also copied .env into the team's password manager. The database dump is useless without the DATA_ENCRYPTION_KEY that encrypted the settings inside it.


16. When Things Go Wrong

Priya kept a list in the team wiki.

Symptom Likely cause
Sign-in loops back to the login page REDIRECT_URL or WHITELISTED_ORIGINS does not match the address in the browser exactly
CORS error in the browser console on sign-in The app's own address is missing from WHITELISTED_ORIGINS
Backend crashes with database errors after an upgrade Migrations were not run for the new version
Collections do not update live for teammates WebSocket traffic is blocked, or VITE_BACKEND_WS_URL is wrong
Requests to localhost fail The browser blocks them. Use the Agent or the extension.
Magic-link emails never arrive SMTP address or credentials are wrong. Check the container logs.
Saved sign-in secrets stopped working DATA_ENCRYPTION_KEY was changed or lost

For anything else, docker compose logs hoppscotch usually says what went wrong in the first few lines after the error.


17. Epilogue: The New Joiner, Again

In December another developer joined, Fathima.

Priya sent her one invite. Fathima signed in with Google and opened the Salna Backend workspace. She picked the Staging environment, filled in her own secret key, ran the login request, and watched the token appear in the environment by itself. Then she ran the whole collection. Forty-one requests, eighty-three tests, all green.

It took her eleven minutes. Nobody sent her a file.

Arun, who had lost his first afternoon to a three-week-old collection, was the one who showed her around. He did not mention the WhatsApp group.


Quick Reference Cheat-Sheet

I want to Do this
Run it docker compose up -d with the all-in-one image and PostgreSQL
Create or update the tables docker compose run --rm --entrypoint sh hoppscotch -c "pnpx prisma migrate deploy"
Serve it on one domain ENABLE_SUBPATH_BASED_ACCESS=true, admin at /admin, backend at /backend
Put HTTPS in front Caddy reverse_proxy 127.0.0.1:3080
Become admin Be the first to sign in at /admin
Stop strangers signing up Invite users from the admin dashboard and allow only invited people
Share collections A team workspace with Owner, Editor and Viewer roles
Avoid repeating addresses Environment variables, written as <<baseUrl>>
Keep a key private Mark the variable as secret, so it stays in your browser
Save a token automatically pw.env.set("accessToken", pw.response.body.access_token)
Write a test pw.test("name", () => pw.expect(pw.response.status).toBe(200))
Reach localhost The Hoppscotch Agent or browser extension as the interceptor
Run tests in CI hopp test collection.json -e env.json
Back it up pg_dump the database nightly, and keep .env safe

Key takeaways

  1. One shared workbench beats fourteen exported files. A team workspace means everyone has the latest collection, always.
  2. Hoppscotch self-hosted is three parts. The app, the backend and the admin dashboard, backed by PostgreSQL, in one container or three.
  3. Sign in first and close the door. The first admin sign-in wins, so do it straight away, then allow only invited users.
  4. Shared settings on your server, secrets in your browser. Use environments for addresses and secret variables for keys.
  5. Let scripts carry the token. A test script that stores the login token removes the most common stale-request mistake.
  6. Pin the version, run migrations, back up the database and the key. That is the whole upgrade and recovery story.

Self-hosting your API workbench means the team's knowledge of its own APIs lives on a server you own, not in a chat group and not in someone else's cloud.