Humble.js uses express and react-router library underneath for routing and server. For you, all the details are abstract and you are only required to provide simple JSON to serve the pages and process API requests.
Humble.js has 2 main components
A part from Node’s default, humble.js has some helpful global read-only variables that can be accessed from anywhere in the application.
Variable Name | Description |
---|---|
logger |
Logger based on winston logging |
cfg |
Configuration data for the application (managed in database table called ConfigData ) - The configurations are updated every 15 minutes - This value can be changed in core/__constants.js . Anything specific in KEY_NAME column of the table is accessed as is. For example, if you have VERSION , you can access it at anytime by cfg.VERSION |
deploymentCfg |
Deployment configuration if you need it, it contains deployment information, e.g, deploymentCfg.SCOPE |
Global variables can be dangerous (thread-safety issues) and also can cause memory problems (memory leaks if not used properly). It is not recommended to define new global variables unless you absolutely need them.
All the database tables are modelled automatically to javascript objects using models/__model.js
and models/__base.js
. For example, if you have a table called Client
, a typical model class will look like:
import { Model, STATUS } from '~/models';
const TABLE_NAME = 'Client';
const QUERIES = {
QUERY_BY_EMAIL: 'SELECT * FROM Client WHERE email = ? AND status = ?',
};
const hasFlag = (record, flag) => record && (record.flag & flag) === flag;
const FUNCTIONS = [
hasFlag,
];
const model = Model({
TABLE_NAME,
QUERIES,
FUNCTIONS,
});
const queryByEmail = ({ email }) => (model.querySingleObject(QUERIES.QUERY_BY_EMAIL, [ // or use model.query if you expect query to return multiple objects
email, STATUS.ACTIVE,
]));
export default {
...model,
queryByEmail,
hasFlag,
};
Now when you query the database, you simply do:
import Client from '~/models/client';
...
Client.queryByEmail({ email: 'blah@example.com' }).then((client) => {
const correct = client.hasFlag(Flags.IS_CORRECTED); // notice single parameter only
});
// -- or you can use Client.hasFlag(myClient, Flags.IS_CORRECTED)
model()
pre-defines some basic queries and corresponding functions
Function | Description |
---|---|
queryAllActive() |
Queries all the active records |
queryById({ id }) |
Queries record by ID |
queryByIds({ ids }) |
Query records by IDs, expects ids to be array of numbers |
queryByPublicId({ publicId }) |
Queries single record by public ID. Public ID is a random string associated with the record. Similar to ID |
If you have an empty object and you want to convert it in to a model
const newClient = Client.createNew({ name: 'blah', email: 'blah@example.com', flag: 4 }); // or Client.convertObject which is an alias to createNew
// -- now you can simply insert the record to the database
// -- or update existing records
newClient.insert().then(() => {
console.log('Record successfully inserted');
});
Default database functions are very handy
Function | Description |
---|---|
insert() |
Inserts current object in to the database |
update() |
Update the current object |
del() |
Deletes the record from the database |
import Client from '~/models/client';
const deleteClient = async (email) => {
const client = await Client.queryByEmail({ email: 'blah@example.com' });
if (client) {
await client.del();
}
}
Humble.js routing are split in to three parts:
API routes are defined in routes/api
directory. An example of such route is
import SigninController from '~/controllers/api/signin';
import * as hjs from '~/core/__middleware'; // Humble.js defined helper middleware
export default [
{
path: '/api/v1/signin',
method: 'POST',
controllers: [
hjs.json, // set response type to JSON
hjs.noCache, // never cache this
SigninController,
],
},
];
Notice controllers
array, these are middleware in order. The last one should send the response. hjs
in above example are just helpers middleware, you can use your own if you want.
Humble.js comes with default session management, which is based on express-session. You can override this by specifying custom routes in routes/sess
directory. For most cases, the default session controllers are good enough.
Page routes are defined in client/routes
directory which are then compiled. These definitions are simple JSON based and no controller information is available here.
An example of most simple route is:
import AboutPage from '~/client/pages/About';
export default [
{
path: '/about',
component: AboutPage,
controllerId: 'about',
},
];
The idea behind above JSON (page route) is, when user hits /about
, the server triggers middleware from about
which is essentially a javascript file controllers/pages/about.js
.
import done from './__final';
export default async (req, res, next) => {
done({
req,
next,
props: {
message: 'Hello from server',
},
});
}
Controllers are considered last middleware before done
is called. Server then puts everything together and passes in to AboutPage
which is react page and hydrates the react DOM. In turn, user sees Hello from server
message on their browser.
done()
function takes other inputs as well:
Field | Description |
---|---|
req |
Request object straight from current middleware |
next |
next() function straight from current middleware |
page |
Page controller (page.js ) is customisable middleware that you can use as basis for every page. done() puts everything from page object to props |
props |
The props for the page component |
meta |
Meta object supported by @humblejs/page |
token |
Any custom JWT token. Typically you will do this in page.js but if you want a custom token for this particular page, you can define it here |
header |
Props specifically for header component of the page |
footer |
Props specifically for footer component of the page |
breadcrumbs |
Breadcrumbs for the page, typically passed in to @humblejs/breadcrumbs component |
structuredData |
Any custom structured data for the page. This is appended to the default one |
cid |
Controller ID for the page, this is auto-generated but if you want to override this for any reason, you can do it by specifying here. Controller ID is file path from controllers/pages/... onwards. For example, if you have a file controllers/pages/admin/find-users.js , the controller ID is admin/find-users by default. |
A typical AboutPage
file will look like this:
import { PageDefinition } from '@humblejs/core';
export default PageDefinition({
name: 'About',
importer: () => import(/* webpackChunkName: "About" */ '@train/pa-about'),
pkg: '@train/pa-about',
});
PageDefinition
lazy loads the page and helps in code-splitting. webpackChunkName
is a magic comment by webpack. Read more about magic comments here
Code splitting is done automatically for each pages, typically coming from importer()
in PageDefinition
. This is because Humble.js uses webpack
for bundling.
Templates define the bare bones of any page or email. They’re found in templates
directory. Humble.js uses EJS templates.
__page.ejs
is default template and you can use custom page template by specifying config data PAGE_TEMPLATE
which will be picked up from templates
directory.
templates/__emails/
is default source for the templates, but you can specify custom source directory in config data EMAIL_TEMPLATE_DIR
.
templates/__errors
is default source for the templates. You can override this in config data ERROR_TEMPLATE_DIR
.
templates/__errors/__generic.ejs
is catch-all for all the errors. You can override catch-all with config data EMAIL_TEMPLATE
which will be picked up from templates/__errors
(or ERROR_TEMPLATE_DIR
if available)
You can add HTTP status codes templates for each type of error templates. For example, if you want custom error page for error 503, you will need to create templates/__errors/503.ejs
Humble.js comes with task automation framework which helps you run frequent tasks by simply scheduling them. All the tasks are defined in crons
directory.
A task is a simple class that has a run function that returns promise.
export default class MyNewTask {
constructor() {
this.name = this.constructor.name;
}
async run() {
const result = { };
return result;
}
}
To run this task manually for development, simply do yarn runjob my-new-task --mock
--mock
will ignore checking of the database entry in Cron
table and will always run.
Once the task is ready for production, simply add an entry in Cron
with preferred schedule
and run_type
. run_type
tells Humble.js whether to run the task from within running application or register it as linux cron.
If you want to manually run the task, simply do yarn runjob <task name>
.