Translate your project into Typescript

In this example, we will guide you through the steps to turn your project into a Typescript project. The main benefit of Typescript is that it offers the ability to add static types to your Javascript code.

Requirements

In this example, we will assume that you are familiar with Typescript and that you have it installed. If this is not the case, simply run the following: npm install -g typescript

To make it easy for you, we created our own typings to help you using our exposed tools using typescript. They are available as @types/forest-express-sequelize and @types/forest-express-mongoose. To get them, simply run: npm install --save-dev @types/forest-express-[sequelize | mongoose]

Configuration

As for every Typescript project, we need to create a configuration file. It helps the transpiler to know where to take the files from, and where to transpile them to. To have a smooth migration into Typescript, we will use the following configuration:

Add this file at the root of your project.

SQL
MongoDB
SQL
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"pretty": true,
"sourceMap": true,
"target": "es2017",
"outDir": "./dist",
"baseUrl": "./",
"types" : ["node", "express", "forest-express-sequelize", "sequelize"],
"allowJs": true
},
"include": ["./**/*", ".env"],
"exclude": ["node_modules", "dist"]
}
MongoDB
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"pretty": true,
"sourceMap": true,
"target": "es2017",
"outDir": "./dist",
"baseUrl": "./",
"types" : ["node", "express", "forest-express-mongoose", "mongoose"],
"allowJs": true
},
"include": ["./**/*", ".env"],
"exclude": ["node_modules", "dist"]
}

This tells the transpiler to take every file using .ts or .js (see allowJs option) as an extension, and to transpile them into a ./dist folder. This is where your transpiled will be. This type of configuration allows you to translate your app into Typescript in dribs and drabs. No need to translate every file at once, change only the files that interest you. Now that your Typescript transpiler is set, we need to update our .package.json with new scripts to fit with the new structure of our project.

package.json
"scripts": {
"start": "node ./dist/server.js",
"legacy-start": "node server.js",
"build": "tsc",
"start-dev": "tsc && tsc --watch & nodemon --watch ./dist ./dist/server.js"
},

Note the last script start-dev is the script to use while developing. It will transpile your sources every time you perform a change, and it will refresh your server. Finally, let's install the mandatory typings for a newly generated project, run this from a command prompt:

SQL
MongoDB
SQL
npm install --save-dev @types/node @types/express @types/sequelize @types/forest-express-sequelize
MongoDB
npm install --save-dev @types/node @types/express @types/mongoose @types/forest-express-mongoose

And that's it! You are now able to code using Typescript in your app. Simply change the extension from .js to .ts , change some code, and your file will be automatically handled.

How it works

Directory: /models

Depending on your ORM (sequelize, mongoose) your models' configuration will change.

SQL
MongoDB
SQL

Sequelize Models

The idea here is to create a Typescript class which will describe how your data should look like, and then init your actual model using sequelize. Let's take a simple user model, generated by Lumber:

models/users.js
module.exports = (sequelize, DataTypes) => {
const { Sequelize } = sequelize;
const Users = sequelize.define('users', {
firstname: {
type: DataTypes.STRING,
},
lastname: {
type: DataTypes.STRING,
},
createdAt: {
type: DataTypes.DATE,
},
updatedAt: {
type: DataTypes.DATE,
},
}, {
tableName: 'users',
schema: process.env.DATABASE_SCHEMA,
});
return Users;
};

Let's create a Typescript class to describe our data, based on this model. Create a folder to store interfaces, and create the users.ts interfaces:

models/interfaces/users.ts
import { Model } from "sequelize";
export default class Users extends Model {
public id!: number;
public firstname!: string;
public lastname!: string;
public readonly createdAt: Date;
public readonly updatedAt: Date;
}

Now our data are typed, let's go back to our model models/users.js , switch the file to Typescript, and change its content with the following:

models/users.ts
import Users from './interfaces/users';
export default (sequelize, DataTypes) => {
Users.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
firstname: {
type: new DataTypes.STRING(128),
allowNull: false
},
lastname: {
type: new DataTypes.STRING(128),
allowNull: false
}
},{
tableName: "users",
modelName: 'users',
sequelize,
}
);
return Users;
}

Note the following:

  • The import statement has changed.

  • We force the modelName property to ensure our model sticks to its previous name.

And that's it! Your model is now defined in typescript, you can use it anywhere you want by importing it using:

import users from './models/interfaces/users';

You can find more resource here about how to use TypeScript with the sequelize ORM:

MongoDB

Mongoose Models

The idea here, is to create a schema as you would do in JS, but also an interface to force the typings of your models. Let's take a simple user model, generate by Lumber:

models/users.js
const mongoose = require('mongoose');
const schema = mongoose.Schema({
'firstName': String,
'lastName': Date,
}, {
timestamps: false,
});
module.exports = mongoose.model('users', schema, 'users');

A translation to typescript would look like the following:

models/users.ts
import { Schema, Document, model} from 'mongoose';
interface IUser extends Document {
firstName: string;
lastName: string;
}
const schema = new Schema({
'firstName': String,
'lastName': Boolean,
}, {
timestamps: false,
});
export default model<IUser>('users', schema, 'users');

Note the following:

  • The import statement has changed

  • We create an interface to specify attribute type

  • We apply the types at the model creation

And that's it! your model is now defined in typescript, you can use it anywhere you want by importing it using:

import users from '../models/users';

You can find more resource here about how to use typescript with the mongoose ODM:

Directory: /routes

Routes consist of a simple router, handling CRUD routes, and using models/record tools to manipulate data.

So there is nothing special to do in this section, but changing the Javascript import statements to Typescript ones. Here are the default routes created for the collection users:

SQL
MongoDB
SQL
routes/users.js
const express = require('express');
const { PermissionMiddlewareCreator } = require('forest-express-sequelize');
const { users } = require('../models');
const router = express.Router();
const permissionMiddlewareCreator = new PermissionMiddlewareCreator('users');
// Create a User
router.post('/users', permissionMiddlewareCreator.create(), (request, response, next) => {
next();
});
// Update a User
router.put('/users/:recordId', permissionMiddlewareCreator.update(), (request, response, next) => {
next();
});
...
// Delete a list of Users
router.delete('/users', permissionMiddlewareCreator.delete(), (request, response, next) => {
next();
});
module.exports = router;
MongoDB
routes/users.js
const express = require('express');
const { PermissionMiddlewareCreator } = require('forest-express-mongoose');
const { users } = require('../models');
const router = express.Router();
const permissionMiddlewareCreator = new PermissionMiddlewareCreator('users');
// Create a User
router.post('/users', permissionMiddlewareCreator.create(), (request, response, next) => {
next();
});
// Update a User
router.put('/users/:recordId', permissionMiddlewareCreator.update(), (request, response, next) => {
next();
});
...
// Delete a list of Users
router.delete('/users', permissionMiddlewareCreator.delete(), (request, response, next) => {
next();
});
module.exports = router;

And here is how your routes file should look like if you want to use typescript:

SQL
MongoDB
SQL
routes/users.ts
import * as express from 'express';
import { PermissionMiddlewareCreator } from 'forest-express-sequelize';
import users from '../models/interfaces/users';
const router = express.Router();
const permissionMiddlewareCreator = new PermissionMiddlewareCreator('users');
// Create a User
router.post('/users', permissionMiddlewareCreator.create(), (request, response, next) => {
next();
});
// Update a User
router.put('/users/:recordId', permissionMiddlewareCreator.update(), (request, response, next) => {
next();
});
// Delete a User
router.delete('/users/:recordId', permissionMiddlewareCreator.delete(), (request, response, next) => {
// Learn what this route does here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/routes/default-routes#delete-a-record
next();
});
...
// Delete a list of Users
router.delete('/users', permissionMiddlewareCreator.delete(), (request, response, next) => {
next();
});
export default router;
MongoDB
routes/users.ts
import * as express from 'express';
import { PermissionMiddlewareCreator } from 'forest-express-mongoose';
import users from '../models/users';
const router = express.Router();
const permissionMiddlewareCreator = new PermissionMiddlewareCreator('users');
// Create a User
router.post('/users', permissionMiddlewareCreator.create(), (request, response, next) => {
next();
});
// Update a User
router.put('/users/:recordId', permissionMiddlewareCreator.update(), (request, response, next) => {
next();
});
// Delete a User
router.delete('/users/:recordId', permissionMiddlewareCreator.delete(), (request, response, next) => {
// Learn what this route does here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/routes/default-routes#delete-a-record
next();
});
...
// Delete a list of Users
router.delete('/users', permissionMiddlewareCreator.delete(), (request, response, next) => {
next();
});
export default router;

Directory: /forest

There is nothing special to do here. Translating your smart configuration (actions, fields, and segments) in .js is as simple as just renaming the file and updating your imports.

Let's take the following example:

SQL
MongoDB
SQL
forest/users.js
const { collection } = require('forest-express-sequelize');
collection('users', {
actions: [{
name: "Promote to admin",
type: 'single',
}],
fields: [],
segments: [],
});
MongoDB
forest/users.js
const { collection } = require('forest-express-mongoose');
collection('users', {
actions: [{
name: "Promote to admin",
type: 'single',
}],
fields: [],
segments: [],
});

Translating this file into Typescript will give the following:

SQL
MongoDB
SQL
forest/users.ts
import { collection } from 'forest-express-sequelize';
collection('users', {
actions: [{
name: "Upgrade to admin",
type: 'single',
}],
fields: [],
segments: [],
});
MongoDB
forest/users.ts
import { collection } from 'forest-express-mongoose';
collection('users', {
actions: [{
name: "Upgrade to admin",
type: 'single',
}],
fields: [],
segments: [],
});