Touring Hyperstack
Like Rails, all Hyperstack apps look the same; largely due to an opinionated, chef's-menu design. Let's take a stroll around the main parts of every Hyperstack app and see that design.
Your environment configuration
config/
db/
environments/
initializers/
Environments: config/environments
Hyperstack codifies environments into Typescript files that look like configuration, but if you need - can do more. Often some pieces of configuration need to be fetched dynamically, which is why it is an async function returning a plain Javascript object. An environment is loaded from a NODE_ENV
or HST_ENV
environment variables.
You also have initializers which is how extensibility is done in Hyperstack, we'll get to that later.
Database: config/db
This place stores your migrations and seeds. Things that belong exclusively to your database layer.
Your app
app/
models/
controllers/
workers/
mailers/
lib/
tasks/
test/
This is very similar to Rails. Each folder contains entities, and an index.ts
file. Although early versions of Hyperstack auto-loaded these, we've come to conclusion that explicit is better than implicit so you have to explicitly import
every file you add.
This give you the ability to control how to load files, order of loading, and more.
Test first development
A special note for testing. In Rails, you had to assemble an array of libraries to set up your database, clean stuff out, and more. With Hyperstack all these best practices are built-in and every test is super clean.
We also advocate the use of snapshot testing for the backend. This makes your tests even cleaner and easy to maintain.
Your tools
src/
jest.config.js
.eslintrc.js
package.json
Test, linting & jest.config.js
Testing configuration. Powered by Jest and Stylomatic. Stylomatic is a one-stop-shop configuration for all Hyperstack projects that deal with styling, linting, and dev tooling configuration.
Dependencies: package.json
Great to check what's in there. Basically some Hyperstack deps and a few dev oriented libs.
Your run scripts
You can use bin/hyperstack
to drive your day-to-day workflow.
Generators
Now that you're done reviewing the app structure, why not run a generator, see what it does?
Generate a model:
$ bin/hyperstack g model Tweet title:string
And a controller (always singular tweet
, we'll pluralize where needed):
$ bin/hyperstack g controller tweet
Or a full CRUD scaffold:
bin/hyperstack g scaffold todo title:string content:text
It's fun to use these as starting points and work on your own stuff from there.
Portal
Run a portal to get an access to a running app:
$bin/hyperstack portal
Tasks
You don't have to have a full on admin. Just codify your errand as a task, and run it from your terminal.
//lib/tasks/forgot-password.ts
import { task } from 'hyperstack'
import { appContext } from '../../app'
import { AuthMailer } from '../../app/mailers/auth'
export default task('send a reset password email.', async (args) => {
const { User } = appContext.models()
const { username } = args
const user = await User.findOne({ where: { username } })
if (!user) {
throw new Error('no such user')
}
await AuthMailer.forgotPassword(user).deliverLater()
return { ok: true }
})
List your available tasks:
$ bin/hyperstack tasks
Tasks
export-notes: export notes.
forgot-password: send a reset password email.
And run them:
$ bin/hyperstack tasks forgot-password --username foobar@example.com
Database productivity
You can manage your database from your terminal.
Seeding
Use config/db/seed.ts
to write scripts that inserts seed data into your database. Usually stuff you always want in development, like a fake user or some ad-hoc data.
Then:
$ bin/hyperstack seed
Will take care of inserting.
Migrations
Hyperstack uses a migration framework to bring a database up to date and perform only the necessary DDL needed for it.
You write migrations in config/db/migrate
much like in Rails or many other frameworks that adopted the same idea from Rails. The files are simple javascript files and uses our Builder
interface to ease up on typing.
Then:
$ bin/hyperstack migrate
Sets up the database. You can also control if you want these kind of migrations (safe and better for production), or prefer and automatic sync (more of a dev thing, saves writing migrations) of entities via configuration.
Accessing your app from code
'app' argument
In some cases where you're building infrastructure or using infra and you're filling up a function (seed, tests, etc.), we give you an app
object which contains:
{
app, // express app
logger,
context
}
This is strictly for convenience, so you could just "grab and go", for example:
task('my task', async(args, {logger} /* this is an app object*/)=>{
logger.info('foobar')
})
But, you can also do this (see appContext
below)
import {appContext} from ...
task('my task', async(args)=>{
const logger = appContext.logger()
})
Using 'appContext'
The recommended way to type dynamically loaded assets such as controllers, models, and workers, is to build one appContext
that you will get these from.
You get an appContext
for free when you use the app generator (yarn create hyperstack
).
Why context?
The reason for using context, is that the app has a certain loading order and some loading order dependency. For example some controllers may rely on initializers providing them props, and these controllers might use these props in compile time.
For this to work - the app has to have been booted and initializers would have to have done their job already -- before loading any controller.
If you import { SomeController } from './<some-controller>'
you will be reaching out for a controller ignoring all of the booting order of the app.
This is why context.models()
or context.controllers()
is how you need to get your app types.
Where is your 'appContext'?
To support static typing of dynamically loading material, we had to create an explicit mapping. This is very similar to how Redux
has its own AppDispatch
for example.
app/
controllers/
mailers/
models/
../
index.ts <-- have your `appContext` here
And a usage example:
const { User } = appContext.models()
Hurray!
This rounds up your tour around a Hyperstack app. Now let's understand a bit more about your dependencies: sqlite
, Redis
and Postgres
.