Introduction

For.io Studio is a next-generation IDE that is specialized for rapid development of web backends. It runs (almost fully) in the browser and it can be used to develop Node.js backends on top of Express.js, using MongoDB. Other programming languages, frameworks and databases will be supported in future.

Instant turnarounds

For.io Studio enables backend development in real time, with immediate turnarounds upon every change in the source code. To make this as smooth as possible, For.io Studio includes Express simulator and MongoDB simulator. This makes it possible to execute a large part of the backend (including database-aware end-to-end API tests) instantly in the browser, resulting in instant feedback about any change being made to the source code.

MongoDB simulator

While the MongoDB simulator is very convenient for a quick & easy start and super-fast execution of the API tests, it only supports a subset of the MongoDB operations. Eventually, you will need to execute the API tests using real MongoDB database, typically running on your machine.

How it works?

Code generation

Architecture

Architecture

Project layout

A typical project generated by For.io Studio consists of the following folders:

  • abstract-model - this folder contains the abstract model (a.k.a. project specification): data models, api specs, test specs and test data. The abstract model serves as an input for the code generation. It is not needed in run-time, so it doesn’t have to be included in the final build artifacts, e.g. when deploying the backend.

  • abstract-model/types - contains type definitions, for instance:

abstract-model/types/Movie.js
exports.Movie = {

    uri: '/movies',

    fields: {
        _id: 'string pkey autogen',
        title: 'string',
        year: 'int'
    },

    UID: '1526e0a'

}
  • abstract-model/api - contains REST API specifications, for instance:

abstract-model/api/getMovie.js
exports.getMovie = {

    route: 'GET /movies/:movieId',

    title: 'Get movie',

    model: 'Movie:read',

    roles: [],

    params: {
        movieId: 'string'
    },

    query: {},

    headers: {},

    cookies: {},

    middleware: ['auth'],

    responses: [200, 403, 404],

    tags: [],

};
  • abstract-model/testdata - the test data that is referenced in the test specifications, which is used for the generation of the database precondition in the API tests. For instance:

abstract-model/testdata/movies.js
exports.Movie1 = {
    _id: 'movie_1',
    title: 'some title',
    year: 2020
}
  • abstract-model/tests - the API tests specifications, for instance:

abstract-model/tests/getMovie.js
exports.getMovie = {

    title: 'Get movie',

    users: ['spock'],

    db: {
        movies: ['#Movie1'],
    },

    requests: [{
        call: 'getMovie',
        params: {
            movieId: 'movie_1'
        },
        query: {},
        body: {},
        headers: {},
        cookies: {}
    }],

}
  • src - the backend implementation, consisting of handlers (and other components), for instance:

src/api/getMovie.js
// A factory for the controller 'getMovie', using dependency injection of singletons: db, types, responses, _.
exports['CONTROLLER getMovie'] = (db, types, responses, _) => {

    // Return the controller, which will be invoked for each request,
    // with the request-scoped dependencies as arguments.
    return async function getMovie(movieId, userId, log) {
        let filter = {
            _id: movieId,
        };

        let data = await db.movies.getOne(filter);
        let movie = types.Movie(data);

        return movie;
    }

}
  • test - manually written tests (units tests & integration tests), mocks and test resources,

  • diligence - auto-generated, database-aware, end-to-end API tests, typically executed by API Diligence, and are also displayed as test reports in For.io Studio.

Code generation

For.io Studio has a first-class, built-in support for code generation. There are few types of code generation that are supported, as described in the following sections.

Occasional code generation

You can bootstrap a project by generating code from a data model and/or api specification ( Code → Generate code or F4 ). This will generate data types, CRUD REST API from the types, as well as API tests for the generated endpoints. After that, the generated source code can be edited / customized (except some files which are continuously generated - see the next section). After modifying a type, you can run the code generation again. If additional types have been created since the last scaffolding, their corresponding CRUD REST API will be generated. The existing files will be overwritten, if they haven’t been modified since they were previously generated.

Continuous code generation

As soon as you generate/scaffold the backend, a few auto-generated files will appear in your project:

  • modules.js - keeps an up-to-date list of projects modules (from both src and test folders)

  • src/types.js - source code of all generated data types, including data validation logic

  • src/routes.js - aggregation of the abstract model specification of all API routes/endpoints

These files are continuously generated every time you rebuild the project. They should not be edited manually (except for debugging and experimentation). Such manual changes will result in conflicts, and need to be explicitely overwritten by you. Most of these files are based on the abstract model (please see the folder abstract-model), so any customization should be done by changing the abstract model.

Dependency injection

For.io has a first-class, built-in support for dependency injection. It consists of two parts:

  • By utilizing code generation, the discovery of components is prearranged at build-time. For.io Studio generates the dependency injection information (e.g. modules.js).

  • Then, during run-time, the backend relies on the open source For.io Runtime library (npm package for-io-runtime) to load the modules and initialize the application components with their dependencies.

This results in a fast discovery of application components/dependencies (very important e.g. for serverless deployments), and avoids the need to explicitely register most of the components, or to search for them at run-time.

For.io supports dependency injection in two different scopes:

  • components with singleton scope, which are injected once (upon initialization),

  • API controller parameters/dependencies with request scope, which are injected every time a controller is invoked to handle an HTTP/API request.

src/api/getMovie.js
exports['CONTROLLER getMovie'] = (db, types, responses) => {   // (1)

    return async function getMovie(movieId, userId) {   // (2)
        let filter = {
            _id: movieId,
            owner: userId,
        };

        let data = await db.movies.getOne(filter);
        let movie = types.Movie(data);

        return movie;
    }

}
  1. db, types and responses are singleton components that are injected when the application starts.

  2. movieId and userId are request-scoped parameters that are provided for each request.