Skip to content

Basics

Both newly developed and existing projects can benefit from the extension by simply changing the parent classes in the target Data Source and Repositories.

Step 1: Configure DataSource

Change the parent class from juggler.DataSource to SequelizeDataSource like below.

pg.datasource.ts
// ...
import {SequelizeDataSource} from 'loopback4-sequelize';

// ...
export class PgDataSource
  extends SequelizeDataSource
  implements LifeCycleObserver {
  // ...
}

SequelizeDataSource accepts commonly used config in the same way as loopback did. So in most cases you won't need to change your existing configuration. But if you want to use sequelize specific options pass them in sequelizeOptions like below:

let config = {
  name: 'db',
  connector: 'postgresql',
  sequelizeOptions: {
    username: 'postgres',
    password: 'secret',
    dialectOptions: {
      ssl: {
        rejectUnauthorized: false,
        ca: fs.readFileSync('/path/to/root.crt').toString(),
      },
    },
  },
};

Note: Options provided in sequelizeOptions will take priority over others, For eg. if you have password specified in both config.password and config.password.sequelizeOptions the latter one will be used.

Step 2: Configure Repository

Change the parent class from DefaultCrudRepository to SequelizeCrudRepository like below.

your.repository.ts
// ...
import {SequelizeCrudRepository} from 'loopback4-sequelize';

export class YourRepository extends SequelizeCrudRepository<
  YourModel,
  typeof YourModel.prototype.id,
  YourModelRelations
> {
  // ...
}

Relations

Supported Loopback Relations

With SequelizeCrudRepository, you can utilize following relations without any additional configuration:

  1. HasMany Relation
  2. BelongsTo Relation
  3. HasOne Relation
  4. HasManyThrough Relation
  5. ReferencesMany Relation

The default relation configuration, generated using the lb4 relation command (i.e. inclusion resolvers in the repository and property decorators in the model), remain unchanged.

INNER JOIN

Check the demo video of using inner joins here: https://youtu.be/ZrUxIk63oRc?t=76

When using SequelizeCrudRepository, the find(), findOne(), and findById() methods accept a new option called required in the include filter. Setting this option to true will result in an inner join query that explicitly requires the specified condition for the child model. If the row does not meet this condition, it will not be fetched and returned.

An example of the filter object might look like this to fetch the books who contains "Art" in their title, which belongs to category "Programming":

{
  "where": {"title": {"like": "%Art%"}},
  "include": [
    {
      "relation": "category",
      "scope": {
        "where": {
          "name": "Programming"
        }
      },
      "required": true // 👈
    }
  ]
}

SQL Transactions

A Sequelize repository can perform operations in a transaction using the beginTransaction() method.

Isolation levels

When you call beginTransaction(), you can optionally specify a transaction isolation level. It support the following isolation levels:

  • Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED (default)
  • Transaction.ISOLATION_LEVELS.READ_COMMITTED
  • Transaction.ISOLATION_LEVELS.REPEATABLE_READ
  • Transaction.ISOLATION_LEVELS.SERIALIZABLE

Options

Following are the supported options:

{
  autocommit?: boolean;
  isolationLevel?: Transaction.ISOLATION_LEVELS;
  type?: Transaction.TYPES;
  deferrable?: string | Deferrable;
  /**
   * Parent transaction.
   */
  transaction?: Transaction | null;
}

Example

// Get repository instances. In a typical application, instances are injected
// via dependency injection using `@repository` decorator.
const userRepo = await app.getRepository(UserRepository);

// Begin a new transaction.
// It's also possible to call `userRepo.dataSource.beginTransaction` instead.
const tx = await userRepo.beginTransaction({
  isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE,
});

try {
  // Then, we do some calls passing this transaction as an option:
  const user = await userRepo.create(
    {
      firstName: 'Jon',
      lastName: 'Doe',
    },
    {transaction: tx},
  );

  await userRepo.updateById(
    user.id,
    {
      firstName: 'John',
    },
    {transaction: tx},
  );

  // If the execution reaches this line, no errors were thrown.
  // We commit the transaction.
  await tx.commit();
} catch (error) {
  // If the execution reaches this line, an error was thrown.
  // We rollback the transaction.
  await tx.rollback();
}

Switching from loopback defaults to sequelize transaction is as simple as this commit in loopback4-sequelize-transaction-example.