Links

What's new

Breaking changes are not fun, but they are necessary to keep improving the Forest Admin product. The new agent architecture is a big step forward in terms of performance, stability, and features.
Forest Admin released its agent in 2016 when Express was the only popular web framework and typing systems were scarcer. The predominant way to do async programming then was with callbacks.
In the ecosystem of the time, the design of our Agent was sound.
  • Its API had a low learning curve because it was based on Express and Sequelize/Mongoose.
  • The accessibility of our API meant a bigger deal than its performance because Forest Admin customers had smaller projects – being easy to use was more important than speed.
  • The company focused on a reduced number of data sources (PostgreSQL, MySQL, Microsoft SQL, MongoDB).
In the meantime, JavaScript’s ecosystem has substantially evolved, and although we could’ve introduced changes in our API at every turn, this would have greatly disturbed our customers’ daily operations.

Agnostic to web frameworks and ORMs

This new Agent no longer cares about either the web framework or the ORM in use – it natively integrates with Express, Fastify, Nest.js, and Koa.
Customers working on other frameworks can still use the Agent by mounting it in standalone mode and by using a reverse proxy.
You can also connect to your database directly and use automatic model introspection: you will no longer need to maintain Sequelize.js models if you are using another ORM for your production app.
We still support Sequelize.js and Mongoose.js, which gives you additional benefits – such as improving code reuse from your application, even though it isn’t required anymore.
createAgent()
.mountOnExpress(expressApp)
.mountOnKoa(koaApp)
.mountOnFastify(fastifyApp)
.mountOnNest(nestApp)
.mountOnStandalone(3351, '0.0.0.0');

Support multiple data sources

With the advent of the Software-as-a-Service (SaaS) industry, the number of data sources that you need to manage has increased.
Many apps now use specific data sources for specific features – ElasticSearch for search, Redis for caching, etc – and mix relational and non-relational databases.
With our first Agent, the paradigm was to have a single database connection, and Forest Admin released a different library for each data source type.
We now support multiple data sources in the same project: you can connect as many as you need, combine relational and non-relational databases, and use different ORMs for each one.
createAgent()
.addDataSource(createMongooseDataSource(mongooseInstance))
.addDataSource(createSequelizeDataSource(sequelizeInstance))
.addDataSource(createSqlDataSource('postgres://localhost:5432/my_database'));
You are also free to implement your connectors to any data source that you want, even to your in-house APIs.

Autocompletion and typing system

In 2016, the vast majority of projects did not use a typing system.
This is no longer the case and we adapted to that change as well: the new agent is completely written in TypeScript ↗, and automatically generates type definitions on the databases that are plugged into your agent.
When coding you will get autocompletion and type-checking on your models.
sandro@forestadmin $ tsc
src/forest/card.ts:6:61: error TS2820
Type '"customerId"' is not assignable to type '"id" | "is_active" | "customer_id"'.
Did you mean '"customer_id"'?
6 collection.addManyToOneRelation('customer', 'customer', { foreignKey: 'customerId' });
~~~~~~~~~~
Found 1 error in src/forest/card.ts:6

Functional approach

We sell a product made by developers for developers.
As such, our customers have a unique understanding of the value and limits of our product, which makes their feedback more invaluable than in any other industry.
We've made a major change in the way we design our API and are now using a more functional approach: instead of having a generated codebase that you modify, you now start from an empty project and add custom behaviors by registering your logic with side-effect-free functions.
As a customer, the new API is much higher-level: you won't be dealing with low-level concepts such as "routes" or "query-string", but instead you will be dealing with "actions", "fields", and "segments".
createAgent().customizeCollection('books', books => {
books.addAction('Allocate ISBN number', {
scope: 'Single',
execute: ...
});
});

Performance

Our first customers were small startups that have grown alongside us. They now harbor dozens of collections and millions of users – we need to be able to scale with them.
Many of the performance improvements that come with this new Agent would not have been possible with the first one because of API stability.
The first Agent’s API is full of compromises between performance and accessibility. Some of those compromises are no longer relevant today, so we have decided to head in a different direction.