Migrate Mongoose files
What has changed from previous versions
In version 8 we introduced the application-wide scopes and dynamic smart action form along with hooks on bulk and global smart actions.
Since version 7, we introduced a new structure of our generated projects to ease the support of multiple databases. This new structure is the major change we need to consider to correctly perform the migration of your files.
The idea of this document is to guide you through the project architecture and make as few changes as possible to benefit from TypeScript.
The key concepts to handle multi databases are the following:
A configuration file to export your connection options to your databases.
A dynamic loading mechanism to dynamically load your models depending on their database connections.
The files related to these key concepts are the files under the /models
and the /config
directories. Let's handles these files first.
Define your models
What to achieve:
Export an interface representing your model in TypeScript
Export a mongoose Schema corresponding to your interface
To migrate our JavaScript models to TypeScript, we will take the following model generated by lumber as an example:
As you can see, it has been generated to be loaded dynamically. This file actually exports a function accepting a mongoose connection as an argument, to link the model to it. This will not be required anymore.
To make this model work using TypeScript, we first need to declare an interface representing data we can encounter in it. We also need to export the mongoose Schema that describes the same data from the interface. Mongoose can only work with schemas, but TypeScript can't correctly infer types from this kind of structure. This is why we need both the interface for coding and the schema for mongoose.
Do note the usage of the interface in the Schema creation (line 10), this is how we link the TypeScript world to the mongoose world. Also, note that the model is not created in this file anymore, this will be done in the models/index.ts
we are about to change in the next section.
Connect to your databases
Goals:
Connect to each of your databases
Import models definition in those connections
The database connections are configured in config/databases.js
. Here is an example of a fresh new generated project with one connection:
This file is responsible for providing the database connections with the corresponding options. In the end, this file should export an array of connections. If we translate this file into TypeScript, we have to export the same database structure, here is an example:
Now your connections are configured, let's load the models for every connection.
A generated project uses a dynamic model instantiation approach. To do so, the index.js
file under the models
folder is responsible for browsing your models and for loading them. Then, the file exports the models and you can access them from anywhere using const { client } = require('./models');
. This works pretty well in JavaScript because you are not forced to strictly type what models
exports. However using TypeScript, this kind of approach is not ideal because the dynamic part of this approach can not be typed, and thus, TypeScript will raise errors stating that models has no exported member client
. Let's break down the index.js
file step by step to understand what is going on inside.
Indexing models by their name (line 21) is the concept we want to avoid to benefit from TypeScript and code completion. Because it is dynamic at run time, TypeScript can't infer the structure of the exported models and thus, fails at compilation time.
For every connection you configured, this code will create the connection to the database and dynamically load the associated models (from line 10 to line 14). We have no choice but to break this dynamic approach and load the models by hand to be able to export them later on. The new static approach will allow TypeScript to infer the type of the exported object, and thus, will provide us with clean code completion.
If you still want to work with such a dynamic loading mechanism, please ensure that either you don't generate .map.js
files, or ensure you skip these files in the filter in the original index.js
snippet. Otherwise, you will encounter an error at runtime such as Model creation error: SyntaxError: Unexpected token ':'
Do note that we still export connections and Mongoose
as objectMapping
. This is still mandatory to allow your app to be correctly initialized.
This might look tedious at first, but in the end, you are now able to use your models with code completion like the following:
Initialize your Liana
The middlewares/forestadmin.js
is pretty straightforward and just needs to be translated from .js
to .ts
. See the following snippet:
Override your routes
For your routes' code, nothing, in particular, should be changed in terms of structure. Just translate your JavaScript code into TypeScript.
To benefit from code completion, just ensure to use your models' interface when you manipulate your records. Here is an example with the client
model we have been using in this documentation:
Note the request: ForestRequest
to have access to user
and query
After line 8, the type of the client
record should be well inferred and your IDE should provide some code completion, related to the IClient
interface. This is because we passed the interface to RecordGetter
as a generic type.
Customize your collections
For your collections' code, nothing, in particular, should be changed in terms of structure.
The difference you will feel now is the code completion provided by your IDE, in conjunction with our Types. For example, If you start to create a smart field, you should be guided and a list of possible attributes to configure should be provided.
Last updated