Getting Started
Accounts and Users
Linting and Code Style


Turbojet is a lean server-side rendered web app that uses Hotwire principles. Turbojet does not lock you into a new library. It's a template, not a library. It instead relies on well-established libraries and provides the glue to connect everything together. At any time you can depart from Turbojet and go your own way.

Turbojet's goal is to be lean and stable. At its core, it uses battle-tested libraries. It's structured as a Monolithic Application.

We believe there is a balance between DRY (Don't Repeat Yourself) principles and copy and paste. A little copy and paste can make a codebase more lean and easy to understand. Too many abstractions and utilities can make a small code base hard to understand and cumbersome to work on.

Turbojet uses a Server Side Rendered (SSR) architecture. This compared to the current industry trend of Single Page Application (SPA) architecture. An SSR application is much faster to develop but is typically not chosen for performance reasons. Turbojet overcomes the performance concerns with the Turbo.js library. This gives the best of both worlds by allowing the development productivity of an SSR application while maintaining the performance of an SPA application.


Turbojet keeps 3rd party packages to a minimum. It only uses libraries that are popular, maintained, time tested and secure.

Client Side Libraries

bootstrap - Used as the main UI framework.

turbo - Used for client side performance.

Server Libraries

express - The main web framework.

express-session - Used for session management.

express-rate-limit - Used for rate limiting.

express-validator - Used for request validation.

date-fns - Used for date manipulation.

handlebars - Used as a template engine.

helmet - Used for security configuration.

knex - Used as a database interface.

lodash - Used as a general utility library.

nodemailer - Used for sending emails.

passport - Used for user authentication.

pino - Used for log management.

stripe-node - User for Stripe integration.

Development Libraries

standard - Used for linting and code style enforcement.

jest - Used for testing.

faker - Used to generate testing data.

supertest - Used for testing Express routes.

nodemon - Used for running and restarting the main service during development.


  ├── assets/
│ └── images/
├── components/
├── config/
│ └── features.js
├── database/
├── emails/
├── models/
├── routes/
│ ├── route-1
│ └── route-2
├── services/
├── utilities/
├── knexfile.js
├── app.js
└── server.js`

The assets folder
Contains image files such as logos and screenshots.

The components folder
Contains common views and partials for layouts, errors, and authentication.

The config folder
Contains configuration files.

The database folder
Contains database migration and seed files.

The emails folder
Contains email template files.

The models folder
Contains database models for the application where methods and queries can be defined.

The routes folder
Contains a folder for each section of the application. Each folder contains a controller (index.js file) for the section and view templates (*.hbs files) for generating a response to the client.

The services folder
Contains files for services such as authentication and database access.

The utilities folder
Contains globally accessible utilities.

The knexfile.js file
Used by knex to configure development and production database settings and credentials.

The app.js file
The main application file for your application.

The server.js file
Starts the app.js file.

Getting Started

Before you begin you will have to have the following,

By default Turbojet uses Postgres. To use another database follow the Knex documentation.


  1. Download the Turbojet source code to a local folder
  2. Rename '.env-template' to '.env' and update the variables in the new file according to the instructions on each line
  3. Run npm install. This installs all the required 3rd party packages.
  4. Run npx knex migrate:latest --env development This runs database migrations creating tables and fields.
  5. Run npx knex seed:run --env development This runs database seeds adding test data to the database.
  6. Run npm run dev This starts the server. The application should now be running on localhost at the port specified in the console.


The assets folder in the root of the project contains icons, screenshots, logos, etc... for use throughout the project. Consider using favicon.io to generate new favicon files for your brand.


A model is a layer between your controllers and the database. Each model contains methods and queries for accessing and modifying data. The following models are included by default,

The account model is responsible for account-level information such as payment and subscription settings.

The authentication request model is responsible for managing sign-in requests.

The thing model is a generic example model.

The user model is responsible for user contact information and settings.


The route folder in Turbojet contains folders for each section of your application. Each folder contains a controller (index.js) and view files (*.hbs).

Turbojet comes with several built-in routes to cover the common operations of a SaaS application. There are three main sections of the app: Public, User, Admin. Public areas do not enforce authentication and are used for your main marketing site and onboarding. The user section is where authenticated users will use your application. The Admin section is for you to manage everything once your application is in production.

When a user is set as a system admin a menu in the profile modal will show which will allow the user to switch from the User section to the Admin section. This section will be hidden if the user is not a System Admin.

The admin-accounts routes are used to manage user's accounts. Here you can view active users and their payment status.

The admin-dashboard routes are meant to be a general dashboard for your admin users. Out of the box, it's a blank slate you can adapt to your specific application

The admin-users routes are used to manage users of the system. This is where user impersonation can be initiated.

The public-home routes are for a basic marketing site for your business.

The public-onboard routes are where users get redirect when they have logged in for the first time but have not yet created an account. Here it will ask them to agree to the Terms of Service and enter their information. This area can be expanded if your application requires users to provide more information upfront.

The user-account routes are where users can view their general account information, see their payment status, and delete their account.

The user-dashboard routes are meant to be a general dashboard for your users. Out of the box, it's a blank slate you can adapt to your specific application.

The user-help routes are a basic support system where users can create cases. When a case is created two emails are sent. The first email is sent to the support email and contains the case information as well as the contact information of the user that submitted the support case. This email also includes a link that will take an administrator to the user's page in the admin section of the application. The second email is sent to both the administrator and the user who submitted the case. This email system is meant to be a lightweight system to be used in the early years of a company.

The user-profile routes are where users can view and change their personal information and settings. Basic settings are included such as date/time preferences.

The user-team routes are where users can manage their team. Users can add and remove other users and change member permission levels.

The user-things routes are meant to be a generic example section. If you are making a Notes application a thing may be a note for you.

It is recommended that when building off the things section you make a copy of it first. Future updates could add features and examples to the things section. It's best to think of it as a current example of best practices.

Below is an example of a typical route,

router.post('', async (req, res, next) => {
try {
// Validate the submitted
await body('name').isLength({ min: 3 }).withMessage('Name field error').run(req)
await body('description').isLength({ min: 3 }).withMessage('Description field error').run(req)
const validationsErrors = validationResult(req).errors

// If the validation fails show the user the errors
if (validationsErrors.length) {
return res.status(422).render('user-things/new', {
thing: { ...req.body, ...req.params },
errors: validationsErrors

// Create the entry in the database
const thing = await thingModel.create({
account_id: res.locals.authenticated_user.account_id,
name: req.body.name,
description: req.body.description,

// Log the action
req.log.info(`thing ${thing[0]} created by user ${res.locals.authenticated_user.user_id}`)

// Redirect to the entries edit page
return res.redirect(`things/${thing[0]}/edit`)
} catch (error) { next(error) }

Route folders can also contain view partials. By default, any handlebars file (.hbs) in a route folder is automatically registered as a partial that can be used in a view. For an example of this see the public-home route folder.

The things route includes jest testing. To execute these tests run npm run test.


A service in Turbojet is a collection of methods and routes to be used throughout the application.

The authentication service is built to manage all authentication operations in the application. It includes the routes used for authenticating users such as "/auth/signin". For for information see the Authentication section.

The health check service includes a controller that executes a simple query in the database and then responds with a success status. This can be used by a third-party monitoring service to check the application.

The database service manages connections to the database. For for information see the Database section.

The email service is used to send emails. For for information see the Emails section.

The payment service manages the connection to Stripe. For for information see the Payments section.


Turbojet uses Knex to interface with the database. The database credentials can be found in the environmental variables. Settings can be modified in the knexfile.js file located in the root folder of the application. Below is a list of common database management commands,

Run the latest migrations. Creates and updates tables and fields.

npx knex migrate:latest --env development

Run the seed command. Deletes the data in the database and adds new test data.

npx knex seed:run --env development

Create a new migration.

npx knex migrate:make create_thing_table

Create a new seed.

npx knex seed:make add_things

Turbojet uses the Faker library to generate random data such as email addresses, phone numbers, and customer names.


Turbojet includes several built-in emails to handle common situations in your application.

The authentication email used to send login codes to the user.

The new account email is sent to the admin when a new account is created.

The new support case admin email is sent to the admin when a user submits a new case from the help section.

The new support case user email is sent to the user as an acknowledgment that a new case has been received.

The team invitation email is sent to a new team member after another user has invited them.

The welcome email is sent to new users after they have completed the onboarding process.

To send your own email you will use the email service as shown in the code below,

// Send welcome email to user
await emailService.send({
to: req.user.email,
subject: 'Welcome!',
view: './emails/welcome.hbs',
data: {
full_name: fullUserName,
account_level: accountLevel

Accounts and Users

Turbojet supports accounts and users out of the box.

An account in Turbojet is a grouping of users. It can be thought of as a Company, Team, or Organization. All users in the system must be members of an account. Any data your users create should be attached to the account. This allows users of an account to come and go without affecting the data that belongs to the organization. Payment information is also saved on the account level.

A User in Turbojet represents a specific person that belongs to the Account. The user model includes contact information, authentication settings, and the permission level for that user in the account. By default, a user can be either a Member or an Owner of an account. A user that is an Owner can add/remove/update other account members. Owners can also edit account details such as the Account Name and payment information. Permission levels can be extended to support other specific needs of your application.

System Admins
The user model includes an is_system_admin field. When this field is set to true the admin console is enabled for that user. The admin console can be accessed by clicking on the profile icon at the top right of the application. Turbojet includes an environmental variable called INITIAL_SYSTEM_ADMIN_EMAIL. When a user logs into the system for the first time and the user's email matches this environmental variable that user is enabled as a system administrator.


Turbojet supports email code authentication out of the box. To sign in a user provides an email address and the system emails the users a short-lived 6 digit code. The user then enters the code in the login screen to finish authentication.

Email code authentication is a great default for any new application. It essentially offloads the most important security mechanism to the user's email system. It also doesn't require the user to have an account on a specific social service such as Google or Microsoft. It removes the need for complex functions such as forgotten password systems.

Turbojet uses the Passport library to do the authentication heavy lifting. This makes it easy to integrate other login methods such as social login or single sign-on (SSO).

When a user is authenticated the authentication service saves the users and account information to res.locals.authenticated_user and res.locals.authenticated_account. Express js provides res.locals as a way to access global variables from routes throughout the application.


By default Turbojet offloads as much as possible onto Stripe. It uses the Stripe Checkout and Custom Portal. All payment-related notifications are handled by Stripe and must be configured in the Stripe portal. Turbojet's only concern is whether or not the current account has an active subscription and for what product.

Supported Payment Setups
Turbojet includes the framework to handle the following payment setups,

  1. One-time purchase products.
  2. Subscriptions with any amount of product and pricing tiers. For example, Basic monthly or Yearly, Premium Monthly or Yearly.

Advanced payment setups such as metered pricing or addon products require further customization.

Stripe Account Configuration
To set up Stripe you will need to do the following,

  1. Create a Stripe account
  2. Get your Stripe accounts secret keys and add them to the environmental variables. Stripe API Keys
  3. Create Products in the Stipe Dashboard. Stripe Products
  4. Update the features config file in your application found at config/features.js. Add the product and pricing id's from Stripe and associate them with feature tiers in your application.
  5. Enable webhooks in Stripe and add your application's webhook endpoint. It is recommended that all webhooks related to subscription status are enabled. Default: https://www.yourcustomdomain.com/payments/webhook. Stripe Webhooks
  6. Configure Stripes notifications to customers. Stripe Subscriptions and Emails Settings
  7. Update your accounts Branding. Stripe Branding

Feature Tiers
Turbojet is set up to have feature tiers. A feature tier is a set of features in your application that is associated with a product the user's account has subscribed to. For example, you may offer a Premium plan. That Premium plan would be mapped to the Premium feature tier in Turbojet. Throughout your application, you can then enable and disable features for the Premium users.

Feature tiers can be configured in the features config file found at config/features.js.

The current feature tier of the user's account is stored in the account table with the current_feature_tier field. This field can be accessed in your routes with res.locals.authenticated_account.current_feature_tier. It can be accessed in view files with ``````.

Turbojet also allows for a Free tier. By default the user is set to the Free tier when they are not subscribed to any Stripe Product. You can also choose to disable everything in the application when on the Free tier thereby forcing the user to update their account to use the application.

By default, Turbojet handles all webhook requests the same. It checks what Stripe customer the webhook applies to, checks the subscription status of that user in Stripe, and then updates that user's feature tier in the account model. This offloads the complex work of keeping an account up to date and subscribed to Stripe. Stripe will worry about notifying the user that their credit card has expired, that their payment method needs security verification, etc... Your application only needs to know whether or not the subscription is active.


Turbojet is set up to be mobile-friendly. By default, users can save a link to your application on their iOS/Android home screen and run the application in the device's default browser. There are several options to extend the mobile functionality which are described below.

Use Full-Screen Mode
The assets/images/site.webmanifest allows you to change how your application runs on mobile. You can change the "display":"browser" property to "display":"fullscreen" to run your application in full screen mode. Changing this will make your application feel more native on mobile. Enabling full-screen mode has several side effects that may or may not work in your situation. You will need to learn more about PWA (Progressive Web App) configuration and limitations if you go down this path.

Use with Cordova or Capacitor
The Cordova and Capacitor projects allow you to wrap your web app in a native app. Both projects provide a Javascript API to access native device API's. This route also allows you to submit your application to the Apple App Store.

Use Hotwire Turbo Templates
The Hotwire project provides some native iOS and Android templates which can be used to convert your project into a native application. These templates replace your application's navigation with native navigation making the application feel native. To see what this looks like in production try the Basecamp or Hey apps. Using the Turbo native templates also allows you to integrate fully with the phone's native API.


Turbojet works out of the box with Bootswatch themes. A list of themes can be found on the Bootswatch site. Themes can be installed by replacing the Bootstrap CSS file in the components/layout.hbs file with the desired themes CSS file. Hosted versions of the Bootswatch themes can be found on JSDelivr.

Linting and Code Style

Turbojet uses standard.js for linting and code-style enforcement. Standard.js is an opinionated library that includes both a linter with predefined eslint rules and a code formatter. This enforces consistency in a project where multiple people will be contributing. To run standard run npx run clean


Turbojet is platform-independent. It can be easily modified to run on any major cloud. Most cloud providers allow you to connect to your project's source repository and deploy on branch changes. You will need to add environment variables to the cloud provider's deployment settings.


Updates can be downloaded from your Gumroad account. To apply the update you can create a new branch and import the updated code. You can then merge these changes into your branch. Depending on how much you have departed from the originally Turbojet template you may decide to be selective on what you merge into your main codebase.