Navigation

LaunchPad Docs

Configure, run, and integrate with our reusable app backend


Introduction

You're reading the developer documentation for the pre-release version of the LaunchPad backend. This guide will walk you through many of the core things such as setting up your local environment, understanding the components, and integrating with external services. Our goal is to provide a clear and efficient pathway for you to fully understand what is happening under the hood and how to best use LaunchPad.

Important Note: LaunchPad is still in pre-release version 0.2.x. As such, not everything is documented yet, and the documentation (and features themselves) are subject to significantly change until the product stabilizes with the version 1.0 release. Use with care.

Local Development Setup

To get started with local development, follow these steps:

  1. Prerequisites:
    • Go (ver. ≥1.23)
    • Docker Desktop (ver. ≥4.37)
    • Git
    • postgresql: ≥17.5
  2.  
  3. Clone the Repository:
    git clone git@github.com:jubulah/launch-pad.git
  4.  
  5. Environment Variables:

    Navigate into the cloned launch-pad directory and create an .env file in the root directory. Configure necessary environment variables. In the pre-release the there are two environment files, an .env file which is already included in the repo, and a .env_git_ignored As the name implies, this second env file is not tracked by git. This is where you should store your JWT_PRIVATE_KEY and STRIPE_SECRET_KEY values.

    
        ENVIRONMENT="development"
        PORT=8080
        APPLICATION_NAME=users-service-bin
    
        # DB_HOST=host.docker.internal  # To Locally Run in Docker
        DB_HOST=localhost # To Locally Run Outside of Docker
        DB_PORT=5432
        DB_USER=YOUR_DATABASE_USERNAME
        DB_PASSWORD=YOUR_SECURE_PASSWORD
        DB_NAME=user_db
        DB_TABLE_USERS=users
    
        # Logging
        LOG_LEVEL=DEBUG
    
        # Keys
        JWT_PUBLIC_KEY=YOUR_PUB_KEY_HERE
        STRIPE_PUBLISHABLE_KEY=YOUR_PUB_KEY_HERE
        
  6. git setup: Copy your user's global .gitconfig to the repo dir (cp ~/.gitconfig ./[PATH_TO_REPO]). This file is .gitignore'ed so it won't get committed. This is so that go inside of Docker will use your github credentials for downloading private go modules. If your config does not already have these lines, add them to the bottom of the config:
    [url "ssh://git@github.com/"]
            insteadOf = https://github.com/
        
  7. Run Docker Containers:

    In production, this runs inside of docker. If you'd like to run it the same way in your local system, you can do so with:

     docker build --ssh default=/Users/YOUR_USERNAME_HERE/.ssh/id_ecdsa -t dkr-users-service . && docker run -p 8080:8080 --env-file .env_git_tracked --env-file .env_git_ignored --name dkr-users-service dkr-users-service
  8. Bypass Docker:

    For local development, you may want to bypass docker to speed up builds and/or limit bandwidth useage. In that case, update your env file use a local database host with DB_HOST=localhost instead of DB_HOST=host.docker.internal for use with docker. Source your enviroment variables into your current terminal session, install dependencies (go mod tidy) and then build with:

    go build -o users-service-bin && ./users-service-bin
  9. The backend server should now be running on http://localhost:8080 (or your configured port).

Managing Seed Data

Seed data is provided for populating your local development database with initial or test data. This allows you to work with a realistic dataset without manually entering information.

Using Seed Data

To apply the default seed data, execute the following command from the project root:

bash ./scripts/db_init.sh

This command will populate the database with users, products, and other necessary entities as defined in the seed data scripts. To wipe your database and start over, just re-run this same script. To go back to a completely blank database (as though you'd never run the seed script), re-run this same script, but select n when the script asks if it should reapply the User database initialization.

WARNING: We recommend that you do not allow this seed script file to be on any production server. It contains database DROP commands which will delete all your production data if a developer accidentally runs the script there.

Changing Seed Data

LaunchPad is currently in pre-release version 0.2.x. and doesn't yet support modifying the seed data. You still might want to modify the data. The most likely things you'll want to change are the string labels for the roles and organizations tables.

Unsupported Steps to Modify the Data (with a pre-release version):

  • Manually change the data in the seed file. This is currently located in scripts/db_init_commands.sql. You can change this however you want, but note that you will cause yourself merge conflicts when updating to the newest version, and until release version 1.0, we do not guaranteed this file will not radically change (including being removed entirely).
  • Directly modify the data after populating it: For example, to use a more education focused role system, run: update roles set role = 'teacher' where role = 'user'; and update roles set role = 'student' where role = 'subscriber'; The IDs will remain the same, so you can change name strings for organizations and/or roles without breaking anything. Note that if you rerun the seed population script, it will undo all these changes.

Troubleshooting FAQs

postgres:

  • installation: guide assumes you installed postgres with homebrew. brew install postgresql@17
  • login: You can login to the db with: psql -U USERNAME_HERE -d DATABASE_HERE If you can't login, confirm postgres is running with brew services list, if it's not running start it with brew services start postgresql@17
  • psql command: if the psql is not available, be sure that after installation, you added it to your user's pathway with a export PATH="/opt/homebrew/opt/postgresql@17/bin:$PATH" line in your rc file (i.e. .zshrc)

API Endpoints

The LaunchPad user service provides RESTful API endpoints for managing users, organizations, and integrations. All endpoints require JWT authentication unless otherwise specified.

Authentication

Most endpoints require a valid JWT token in the Authorization header. The token must have the appropriate permissions for the requested operation.

The middleware will allow your JWT to have almost any data shape, but it does require the following:

  • id, role.id, organization.id: This requires an id value at the root level for the user and a nested id value for organization and for role.
  • permissions: This requires an array of permission strings. (Note: You can inject the perm strings before sending them to the middleware to keep them out of the token sent to the user.)
  • exp / iat: These will be validated to confirm a proper issue time and that the token is not expired.

Example minimum acceptable token:

{
    "exp": 1791842936,
    "iat": 1738880336,
    "permissions": [ "string", "string", "string" ],
    "organization": { "id": "string" },
    "role": { "id": "string" },
    "id": "string"
}

Users API

Endpoints for managing user accounts and authentication.

Method Endpoint Description Permissions Required Authentication
POST/api/v1/userCreate a new user accountUsers.CreateJWT Required
GET/api/v1/userRetrieve all usersUsers.ReadJWT Required
GET/api/v1/user/:userIDRetrieve user by IDUsers.ReadJWT Required
PATCH/api/v1/user/:userIDUpdate user informationUsers.UpdateJWT Required
DELETE/api/v1/user/:userIDDelete user accountUsers.DeleteJWT Required
POST/api/v1/user/loginUser login (returns JWT token)NoneNo Auth Required
POST/api/v1/user/renew_tokenRenew JWT tokenUsers.ReadJWT Required

Organizations API

Endpoints for managing organization/company information.

Method Endpoint Description Permissions Required Authentication
POST/api/v1/orgCreate a new organizationOrgs.CreateJWT Required
GET/api/v1/orgRetrieve all organizationsOrgs.ReadJWT Required
GET/api/v1/org/:orgIDRetrieve organization by IDOrgs.ReadJWT Required
PATCH/api/v1/org/:orgIDUpdate organization informationOrgs.UpdateJWT Required
DELETE/api/v1/org/:orgIDDelete organizationOrgs.DeleteJWT Required

User Integrations API

Endpoints for managing external service integrations, particularly Stripe Connect accounts.

Method Endpoint Description Permissions Required Authentication
GET/api/v1/user_integration/statusCheck if service is runningUsers.ReadJWT Required
POST/api/v1/user_integration/stripe/accountsCreate Stripe Connect accountUsers.ReadJWT Required
POST/api/v1/user_integration/stripe/:externalID/account_linksGenerate Stripe onboarding linkUsers.ReadJWT Required
POST/api/v1/user_integration/stripe/webhookStripe webhook endpointNoneNo Auth Required

API Response Format

All API responses follow a consistent format:

{
  "success": true|false,
  "message": "Response message",
  "data": { ... },
  "error": null
}

Error Handling

The API returns appropriate HTTP status codes and error messages:

  • 200 OK: Request successful
  • 201 Created: Resource created successfully
  • 400 Bad Request: Invalid request data
  • 401 Unauthorized: Authentication required or invalid
  • 403 Forbidden: Insufficient permissions
  • 404 Not Found: Resource not found
  • 500 Internal Server Error: Server error

Database Schema

The LaunchPad user service uses a PostgreSQL database with the following table structure:

Users Table

The core users table stores user account information and authentication details.

Column Type Nullable Default Description
iduuidNOT NULLuuid_generate_v4()Primary key
emailtextUser email address (unique)
passwordcharacter varying(255)NOT NULLHashed password
name_firstcharacter varying(255)First name
name_lastcharacter varying(255)Last name
birthdatetextDate of birth
address_line_1textPrimary address line
address_line_2textSecondary address line
address_citytextCity
address_statetextState/Province
address_postal_codetextPostal/ZIP code
address_countrytextCountry
organization_iduuidNOT NULLForeign key to organizations table
role_iduuidNOT NULLForeign key to roles table
statusintegerNOT NULLUser account status
created_attimestamp with time zoneCURRENT_TIMESTAMPRecord creation timestamp
updated_attimestamp with time zoneCURRENT_TIMESTAMPRecord update timestamp
deleted_attimestamp with time zoneSoft delete timestamp

Indexes: Primary key on id, unique constraint on email

Foreign Keys: References organizations(id) and roles(id)

Organizations Table

Stores organization/company information that users belong to.

Column Type Nullable Default Description
iduuidNOT NULLuuid_generate_v4()Primary key
org_nametextOrganization name
address_1textPrimary address line
address_2textSecondary address line
citytextCity
statetextState/Province
ziptextPostal/ZIP code
phone_numbertextContact phone number
created_attimestamp with time zoneRecord creation timestamp
updated_attimestamp with time zoneRecord update timestamp
deleted_attimestamp with time zoneSoft delete timestamp

Indexes: Primary key on id

Referenced by: users.organization_id foreign key

Roles Table

Defines user roles and permissions within the system.

Column Type Nullable Default Description
iduuidNOT NULLuuid_generate_v4()Primary key
roletextRole name/description
created_attimestamp with time zoneRecord creation timestamp
updated_attimestamp with time zoneRecord update timestamp
deleted_attimestamp with time zoneSoft delete timestamp

Indexes: Primary key on id

Referenced by: users.role_id foreign key

User Integrations Table

Stores external service integrations for users (e.g., Stripe accounts).

Column Type Nullable Default Description
iduuidNOT NULLuuid_generate_v4()Primary key
user_iduuidNOT NULLForeign key to users table
external_idcharacter varying(255)External service identifier
integration_providercharacter varying(255)Integration service name
statusintegerNOT NULLIntegration status
created_attimestamp with time zoneCURRENT_TIMESTAMPRecord creation timestamp
updated_attimestamp with time zoneCURRENT_TIMESTAMPRecord update timestamp
deleted_attimestamp with time zoneSoft delete timestamp

Indexes: Primary key on id

Foreign Keys: References users(id)

Database Relationships

  • Users → Organizations: Many-to-one relationship via organization_id
  • Users → Roles: Many-to-one relationship via role_id
  • User Integrations → Users: Many-to-one relationship via user_id

Note: All tables use soft deletes with deleted_at timestamps and include standard audit fields (created_at, updated_at).

Stripe Integration

LaunchPad supports letting your users create individual Express Stripe accounts which are linked as a subset of your centralized stripe account.

Onboarding Process

You must direct your users through a multi-step process.

  1. Optional: The more information you've collected in advance from your user, the more info that will be sent during their Stripe account creation. This is optional because Stripe will collect the any info you've left out, but by collecting it in advance, your users will only have to provide their details to once.
  2. Create an Account: /user_integration/stripe_create The endpoint will allow you to create an account for your user inside of your own business stripe account. Stripe returns an ID value which is stored for your user as user_integrations.external_id.
  3. Link an Account: /user_integration/stripe_link This endpoint generates the URL you need to provide to your user to do complete the Stripe onboarding process. Your users will not be eligible to receive payments until they have completed this inside of Stripe's system. This URL expires relatively quickly, and can only be visited once. You will have to call /stripe_link again to generate a new URL if the user doesn't complete their Stripe onboarding in a timely manner.

Webhooks

LaunchPad natively supports receiving webhooks at /user_integration/stripe_connect_webhook. You must manually configure your stripe account to use this web hook in your Stripe Developer Dashboard.

There are over 270 individual event types that Stripe makes available to you via a webhook. LaunchPad supports the specific event types required to ensure you can manage your connected users and get them paid. You should add listeners for these specific events:


Account:
  • account.updated: Occurs whenever an account status or property has changed.
  • account.application.deauthorized: Occurs whenever a user deauthorizes an application. Sent to the related application only.

Balance:
  • balance.available: Occurs whenever your Stripe balance has been updated (e.g., when a charge is available to be paid out).
    By default, Stripe automatically transfers funds in your balance to your bank account on a daily basis. This event is not fired for negative transactions.

Capability:
  • capability.updated: Occurs whenever a capability has new requirements or a new status.

Charge:
  • charge.failed: Occurs whenever a failed charge attempt occurs.
  • charge.refunded: Occurs whenever a charge is refunded, including partial refunds.
  • charge.succeeded: Occurs whenever a charge is successful.
  • charge.dispute.created: Occurs whenever a customer disputes a charge with their bank.

Payout:
  • payout.failed: Occurs whenever a payout attempt fails.
  • payout.paid: Occurs whenever a payout is expected to be available in the destination account.
    If the payout fails, a payout.failed notification is also sent, at a later time.

We integrate with Sripe's Go API v82.3.x. For additional information on Stripe processes and requirements see: Stripe Docs > Stripe-hosted Onboarding

Architecture Overview

Launch Independently of Your Existing Infra
You can use LaunchPad as a standalone addition to your existing apps.

Launch Everything Together / Mix Match
You can alternatively use the IaC provided with LaunchPad to create an AWS instance with everything you need to run your own apps alongside LaunchPad. You can also have a combination of both your own cloud and a LaunchPad cloud.

Here's an example nginx config for routing traffic to LaunchPad.
This is one viable way, but you can route traffic into LaunchPad however you are currently handling your other apps.


# NGINX Config: LaunchPad
server {
    server_name launchpad.YOUR_DOMAIN.com;

    # You don't need root unless you're also hosting web files on this subdomain.
    # root /SERVER_LOCATION/launchpad;
    # index index.html;

    location / {
        proxy_pass http://localhost:8080;
    }

    # Use the HTTPS configuration appropriate for your setup
    listen [::]:443 ssl;
    listen 443 ssl;

    # setup your ssl_certificate
    # setup your ssl_certificate_key
    # setup your ssl nginx conf
    # setup your ssl_dhparam
}

# HTTP to HTTPS Redirection
server {
    if ($host = launchpad.YOUR_DOMAIN.com) {
        return 301 https://$host$request_uri;
    }

    listen 80;
    listen [::]:80;

    server_name launchpad.YOUR_DOMAIN.com;
    return 404;
}

Infrastructure as Code (IaC)

Work In Progress: Not Yet Fully Documented
The IaC included with Jubulah's Launch Kit add-on currently contains two configuration options.

  • terraform-mvp: This creates a production ready AWS setup.
  • terraform-prototype: This is a less complex (and cheaper) setup that will work, but should only be used for prototyping.

terraform-mvp

The goal of terraform-mvp is to provide a modern, automated, and scalable solution to the launching MVP apps.

infrastructure diagram - mvp

Est. Cloud Costs: $70 - $85 / month

  • Elastic Cloud Compute (EC2): $6-11
  • Virtual Private Cloud (VPC): $4
  • Elastic IP for instance: $4
  • Elastic IP for NAT Gateway: $4
  • NAT Gateway: $33
  • Relational Database Service (RDS): $15-25
  • Tax: $4

. . . . . . . . . . . . . . . . . . . . . . . . . .

terraform-prototype

The goal of terraform-prototype is to provide a secure, low-cost, bare-bones server install for you to test your ideas. When your project is ready for launch, you should switch to terraform-mvp.

infrastructure diagram - prototype

Est. Cloud Costs: $20 / month

  • Elastic Cloud Compute (EC2): $10
  • Elastic IP: $4.50
  • Virtual Private Cloud (VPC): $4
  • Tax: $1

Deployments

Work In Progress: Not Yet Fully Documented
This assumes you are running Intel/AMD 64 (X86). If running ARM, then use GOARCH=arm64 instead.

  1. GOOS=linux GOARCH=amd64 go build -o launchpad-x86_64_bin
  2. scp that to your server
  3. Run the binary from your server (in a session persistent manner)


Copyright © 2025 Jubulah Labs. All rights reserved.