Quick Start

This is a guide for those who are using Dominion framework for the first time. If you already have some experience with it, this page probably not going to be very interesting.

OK, if you are still with me, let’s begin. Our main goal in this tutorial is to install and start Node.js API server with a couple of sample APIs.

To start, create a new folder and run following commands in your terminal.

npm init -y
npm i @dominion-framework/dominion
npx dominion create hello
npm start

Message in the terminal should indicate that server is running, like in example below.

$ npm init -y
Wrote to /home/user/dominion-test/package.json:

{
  "name": "dominion-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}


$ npm i @dominion-framework/dominion
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN dominion@1.0.0 No description
npm WARN dominion@1.0.0 No repository field.

+ @dominion-framework/dominion@0.2.7
added 1 package from 1 contributor and audited 1 package in 0.455s
found 0 vulnerabilities

$ npx dominion create hello
Component 'Hello' created in /home/user/dominion-test/components/hello.
$ npm start

> dominion@1.0.0 start /home/user/dominion-test
> node index.js

Server is running at http://localhost:7042/ in development mode...

If port 7042 is busy on your machine, first tell me why? in Gitter. Then you may change it in ./config/config.dev.js.

Now as your server is running, open http://localhost:7042/hello?offset=70&limit=42 to check results. If you can see welcome message, we are done. That’s all you need to start REST API server.

But let’s go a bit deeper to what just happened. First two commands npm init and npm i @dominion-framework/dominion are obvious, initiating npm package and installing Dominion framework. However, after opening ./node_modules, some of you will be pleased to see that it doesn’t contain a backup of npm repository.

Third command npx dominion create hello is creating project scaffold. Actually, it creates a scaffold for a single component. You can read more about it on page Components. But hello is a bit special, it also creates default configs, server’s index file, and adds start script to package.json. Don’t worry, if any of those already exists it won’t be overwritten.

Finally, npm start is executing node index.js. Nothing special here, so let’s see what is inside index file.

const Server = require("@dominion-framework/dominion");

Server.addComponent(require("@dominion-framework/dominion/components/cors"));
Server.addComponent(require("@dominion-framework/dominion/components/logging"));

Server.addComponent(require("./components/hello"));

Server.start();

Server.openApiToFile();

As you may guess from the code above, ./index.js is used for registering components in a project. Yeah, I’m annoyed how verbose requires are as well, but I assure you it will be fixed as soon as ES modules will be moved out of a flag in Node.js.

First two are default components. cors is adding proper CORS headers into APIs response. It is not needed, if your APIs won’t be used from a browser. And logging is writing logs into a console, you should already saw it after opening http://localhost:7042/hello.

Then goes component we created using “npx dominion create”. And here it starts to be interesting. There are 3 files inside folder ./components/hello/ - index.js, controller.js and factory.js. Index is used for registering parts of a components (more about it on Components page). Controller is a file where you write actual API handlers. Lets take endpoint that returns welcome message:

GET: [
    //hello?offset=0&limit=10
    function (offset = 0, limit = 10) {
        // @summary: Demo endpoint with optional arguments
        // @description: Open http://localhost:7042/hello?offset=0&limit=10 to see results

        return HelloFactory.new({message: `Welcome to Dominion! Nice to meet you! [${offset}, ${limit}]` });
    },
    ...
]

Callback function (or handler) is inside GET array what means it will be called on HTTP GET request. Function has two optional arguments - offset and limit - this allows to pass those parameters in a query string. Note, if you will have parameter in URL query string which is not among arguments, then handler won’t be matched. For example, calling http://localhost:7042/hello?foo=bar will return 501 Not Implemented.

Lines @summary and @description are handler’s annotations. They may provide some meta data about handler or extend its functionality. More about them on Annotations page.

The final piece of a puzzle is where /hello? part of URL comes from. Controllers may (but not have to) be linked to a models factory . If they are, URLs path is taken from model’s name. In our case, controller is linked to model named Hello. What brings us to Models.

Models are created by the factory declared in the file factory.js.

{
    name: "Hello",
    
    properties: {
        id: Property.id(),
        message: Property.string(),
        guid: Property.string().example("123e4567-e89b-12d3-a456-426655440000"),
        email: Property.string().example("my.name@example.com"),
        state: Property.enum(["open", "close"]),
        parentId: Property.model("Hello").private(),
        creationTime: Property.date().private(),
        modificationTime: Property.date().private()
    },
    
    factory: { },
    
    instance: {
        sendWelcomeEmail() {
            NodeMailer.send({to: this.email})  
        } 
    }
}

In factory declaration you can specify model name and properties (fields). Properties are defined using Property constructor. It works similar to @hapi/joi. Besides value validation, it allows to add meta data (.example()) or modify output. For example, if field is .private() it will be removed from API response.

When you need to extend functionality, you can add custom methods to factory or model (aka. instance) prototypes. In example above, instances of model Hello will have method sendWelcomeEmail. You can use it like this:

const HelloFactory = Factories("Hello");

HelloFactory.new({
   id: 42,
   email: "my.name@example.com"
})
.then(modelHello => modelHello.sendWelcomeEmail());

As you notices, creating new instance of Hello model is asynchronous operation. Realistically, there is nothing to do asynchronously when you create new model instance. But for a majority of other operation there is a lot, e.g. saving in a database, reading file, communicating with other micro-services, etc. So, to keep things consistent .new() returns Promise.

And as usual, you can find more about models on Factories page.

I hope you are still following, we are almost done. Let’s return back to component index file. We are left with one more row, really simple one:

Server.start();

It runs all bootstrap functions and starts listening for HTTP requests.

OK, that’s it. We just went through all basic features of the framework. Thank you for reading, and I wish you to have a grate time working with Dominion framework. You can find more in-depth information on this website and of course (and it is more recommended) in the source code on GitHub.

One last thing before you leave. There is a little giveaway for you. OpenAPI (Swagger) documentation is coming out of the box - open https://editor.swagger.io/ and copy-paste content from file ./openapi.json into it. This file was created with the last line in a component index:

Server.openApiToFile();