GQLoom

Mikro ORM

GQLoom's foundation for Mikro ORM is currently in an experimental stage. Some common features may be missing. Existing features are not well-tested. Please feel free to report any issues on GitHub.

MikroORM is an excellent TypeScript ORM with support for a variety of databases, such as MySQL, PostgreSQL, SQLite, and so on.

@gqloom/mikro-orm provides integration of GQLoom with MikroORM:

  • Using MikroORM's Entity Schema as silk;
  • Weaving silk into MikroORM's Entity Schema;
  • Generate GraphQL operations from MikroORM's Entity Schema.

Installation

npm i @gqloom/core @gqloom/mikro-orm @mikro-orm/core

You can find more information about the installation in the Mikro ORM documentation.

Using MikroORM's Entity Schema as a Silk Thread

Use the mikroSilk method to use MikroORM's Entity Schema as a silk:

interface IBook {
  ISBN: string
  sales: number
  title: string
  isPublished: boolean
  price: number
  tags: string[]
  author: Ref<IAuthor>
}
 
interface IAuthor {
  name: string
}
 
const Author = mikroSilk(
  new EntitySchema<IAuthor>({
    name: "Author",
    properties: {
      name: { type: "string" },
    },
  })
)
 
const Book = mikroSilk(
  new EntitySchema<IBook>({
    name: "Book",
    properties: {
      ISBN: { type: "string", primary: true },
      sales: { type: "number", hidden: false },
      title: { type: "string" },
      isPublished: { type: Boolean },
      price: { type: "number", nullable },
      tags: { type: "string[]", array: true },
      author: { entity: () => Author, kind: "m:1", ref: true },
    },
  }),
  { description: "A book" }
)

Customizing type mappings

To accommodate more database column types, we can extend GQLoom to add more type mappings.

First we use MikroWeaver.config to configure the type mappings. Here we import the GraphQLDateTime and GraphQLJSONObject scalars from graphql-scalars, and when we encounter the date, jsonb types, we map them to the corresponding GraphQL scalars.

import { GraphQLDateTime, GraphQLJSONObject } from "graphql-scalars"
import { MikroWeaver } from "@gqloom/mikro-orm"
 
const mikroWeaverConfig = MikroWeaver.config({
  presetGraphQLType: (property: EntityProperty) => {
    switch (MikroWeaver.extractSimpleType(property.type)) {
      case "date":
        return GraphQLDateTime
      case "json":
        return GraphQLJSONObject
    }
  },
})

Weaving Silk into MikroORM's Entity Schema

GQLoom's ability to weave silk into MikroORM's Entity Schema allows us to define Entity Schema with less code. In the following example, we will use Valibot Schema as the silk to define MikroORM's Entity Schema.

Creating the weaver

First, we need to create a function that weaves the Valibot Schema into MikroORM's Entity Schema:

import {
  type CallableEntitySchemaWeaver,
  EntitySchemaWeaver,
} from "@gqloom/mikro-orm"
import {
  type ValibotSchemaIO,
  valibotSilk,
  ValibotWeaver,
} from "@gqloom/Valibot"
 
// Use only `valibotSilk` without additional configuration.
export const weaveEntitySchema: CallableEntitySchemaWeaver<ValibotSchemaIO> =
  EntitySchemaWeaver.createWeaver<ValibotSchemaIO>(valibotSilk)
 
// Using `valibotSilk` with additional configuration
export const weaveEntitySchema1: CallableEntitySchemaWeaver<ValibotSchemaIO> =
  EntitySchemaWeaver.createWeaver<ValibotSchemaIO>(
    ValibotWeaver.useConfig(valibotWeaverConfig)
  )
 
// Customizing the Mapping of GraphQL Types to Database Column Types
export const weaveEntitySchema2: CallableEntitySchemaWeaver<ValibotSchemaIO> =
  EntitySchemaWeaver.createWeaver<ValibotSchemaIO>(valibotSilk,{
    getProperty: (gqlType, field) => {
      const columnType = (() => {
        if (extensions.mikroProperty?.primary === true) return PostgresColumn.id
        if (gqlType instanceof GraphQLObjectType) return PostgresColumn.jsonb
        switch (gqlType) {
          case GraphQLString:
            return PostgresColumn.text
          case GraphQLInt:
            return PostgresColumn.integer
          case GraphQLFloat:
            return PostgresColumn.float
          case GraphQLBoolean:
            return PostgresColumn.bool
          case GraphQLJSON:
          case GraphQLJSONObject:
            return PostgresColumn.jsonb
          case GraphQLID:
            return PostgresColumn.id
        }
      })()
      return columnType ? { columnType } : undefined
    },
  })

Defining MikroORM's Entity Schema

In the above code, we created the weaveEntitySchema function to weave the Valibot silk into the Entity Schema of MikroORM. Now we will use the weaveEntitySchema function to define the Mikro Entity Schema.

import * as v from "valibot"
import { asField } from "@gqloom/valibot"
import { weaveEntitySchema } from "./weaveEntitySchema"
 
const Author = weaveEntitySchema(
  v.object({
    id: v.pipe(
      v.string(),
      asField({ extensions: { mikroProperty: { primary: true } } })
    ),
    name: v.string(),
  }),
  {
    name: "Author",
    indexes: [{ properties: ["name"] }],
  }
)

In the above code, we take the object of Valibot as the first parameter of weaveEntitySchema and pass the configuration of the EntitySchema in the second parameter. Here, we have defined an Author entity with id and name attributes, where id is the primary key of the entity, and in this case, we have also added an index to the name attribute.

Defining Relationships

In MikroORM, a relationship is a way to link multiple entities together. In GQLoom, we can use weaveEntitySchema.withRelations to define relationships for entities.

import * as v from "valibot"
import { asField } from "@gqloom/valibot"
import { manyToOne } from "@gqloom/mikro-orm"
import { weaveEntitySchema } from "./weaveEntitySchema"
 
const Book = weaveEntitySchema.withRelations(
  v.object({
    ISBN: v.pipe(
      v.string(),
      asField({ extensions: { mikroProperty: { primary: true } } })
    ),
    sales: v.number(),
    title: v.string(),
  }),
  {
    author: manyToOne(() => Author, { nullable: true }),
  }
)

In the code above, we used weaveEntitySchema.withRelations to define a many-to-one relationship on author for the Book entity, which points to the Author entity, and the relationship is optional, and you can define more information about the configuration of the author relationship in the second parameter of manyToOne. configuration of the relationship.

Resolver Factory

GQLoom provides the ability to generate GraphQL operations directly from MikroORM's Entity Schema, which greatly simplifies the development process.

First, we need to create the Resolver Factory from the Entity Schema.

import { EntitySchema } from "@mikro-orm/core"
import { MikroResolverFactory, mikroSilk } from "@gqloom/mikro-orm"
 
interface IGiraffe {
  id: string
  name: string
  birthday: Date
  height?: number | null
}
 
const Giraffe = mikroSilk(
  new EntitySchema<IGiraffe>({
    name: "Giraffe",
    properties: {
      id: { type: "number", primary: true },
      name: { type: "string" },
      birthday: { type: "Date" },
      height: { type: "number", nullable: true },
    },
  })
)
 
const giraffeResolverFactory = new MikroResolverFactory(Giraffe, () => orm.em)

In the code above, we first define a Giraffe entity and then create a resolver factory using MikroResolverFactory. From the factory, we can generate GraphQL operations directly.

const giraffeResolver = resolver.of(Giraffe, {
  giraffe: giraffeResolverFactory.FindOneQuery(),
  giraffes: giraffeResolverFactory.FindManyQuery(),
  createGiraffe: giraffeResolverFactory.CreateMutation(),
  updateGiraffe: giraffeResolverFactory.UpdateMutation(),
  deleteGiraffe: giraffeResolverFactory.DeleteOneMutation(),
})
 
const schema = weave(giraffeResolver)

With the above simple code, we can generate GraphQL operations from Giraffe entities and weave them into a GraphQL Schema using the weave function. The resulting complete GraphQL Schema looks like this:

type Query {
  giraffe(id: ID!): Giraffe!
  giraffes(
    limit: Int
    orderBy: GiraffeFindManyOptionsOrderBy
    skip: Int
    where: GiraffeFindManyOptionsWhere
  ): [Giraffe!]!
}
 
type Mutation {
  createGiraffe(data: GiraffeCreateInput!): Giraffe!
  deleteGiraffe(id: ID!): Giraffe
  updateGiraffe(data: GiraffeUpdateInput!): Giraffe!
}
 
type Giraffe {
  birthday: String!
  height: Float
  id: ID!
  name: String!
}
 
input GiraffeCreateInput {
  birthday: String!
  height: Float
  id: ID
  name: String!
}
 
input GiraffeFindManyOptionsOrderBy {
  birthday: MikroQueryOrder
  height: MikroQueryOrder
  id: MikroQueryOrder
  name: MikroQueryOrder
}
 
input GiraffeFindManyOptionsWhere {
  birthday: StringMikroComparisonOperators
  height: FloatMikroComparisonOperators
  id: IDMikroComparisonOperators
  name: StringMikroComparisonOperators
}
 
input GiraffeUpdateInput {
  birthday: String
  height: Float
  id: ID!
  name: String
}
 
enum MikroQueryOrder {
  ASC
  ASC_NULLS_FIRST
  ASC_NULLS_LAST
  DESC
  DESC_NULLS_FIRST
  DESC_NULLS_LAST
}
 
input FloatMikroComparisonOperators {
  """
  <@
  """
  contained: [Float!]
 
  """
  @>
  """
  contains: [Float!]
 
  """
  Equals. Matches values that are equal to a specified value.
  """
  eq: Float
 
  """
  Greater. Matches values that are greater than a specified value.
  """
  gt: Float
 
  """
  Greater or Equal. Matches values that are greater than or equal to a specified value.
  """
  gte: Float
 
  """
  Contains, Contains, Matches any of the values specified in an array.
  """
  in: [Float!]
 
  """
  Lower, Matches values that are less than a specified value.
  """
  lt: Float
 
  """
  Lower or equal, Matches values that are less than or equal to a specified value.
  """
  lte: Float
 
  """
  Not equal. Matches all values that are not equal to a specified value.
  """
  ne: Float
 
  """
  Not contains. Matches none of the values specified in an array.
  """
  nin: [Float!]
 
  """
  &&
  """
  overlap: [Float!]
}
 
input IDMikroComparisonOperators {
  """
  <@
  """
  contained: [ID!]
 
  """
  @>
  """
  contains: [ID!]
 
  """
  Equals. Matches values that are equal to a specified value.
  """
  eq: ID
 
  """
  Greater. Matches values that are greater than a specified value.
  """
  gt: ID
 
  """
  Greater or Equal. Matches values that are greater than or equal to a specified value.
  """
  gte: ID
 
  """
  Contains, Contains, Matches any of the values specified in an array.
  """
  in: [ID!]
 
  """
  Lower, Matches values that are less than a specified value.
  """
  lt: ID
 
  """
  Lower or equal, Matches values that are less than or equal to a specified value.
  """
  lte: ID
 
  """
  Not equal. Matches all values that are not equal to a specified value.
  """
  ne: ID
 
  """
  Not contains. Matches none of the values specified in an array.
  """
  nin: [ID!]
 
  """
  &&
  """
  overlap: [ID!]
}
 
input StringMikroComparisonOperators {
  """
  <@
  """
  contained: [String!]
 
  """
  @>
  """
  contains: [String!]
 
  """
  Equals. Matches values that are equal to a specified value.
  """
  eq: String
 
  """
  Full text. A driver specific full text search function.
  """
  fulltext: String
 
  """
  Greater. Matches values that are greater than a specified value.
  """
  gt: String
 
  """
  Greater or Equal. Matches values that are greater than or equal to a specified value.
  """
  gte: String
 
  """
  ilike
  """
  ilike: String
 
  """
  Contains, Contains, Matches any of the values specified in an array.
  """
  in: [String!]
 
  """
  Like. Uses LIKE operator
  """
  like: String
 
  """
  Lower, Matches values that are less than a specified value.
  """
  lt: String
 
  """
  Lower or equal, Matches values that are less than or equal to a specified value.
  """
  lte: String
 
  """
  Not equal. Matches all values that are not equal to a specified value.
  """
  ne: String
 
  """
  Not contains. Matches none of the values specified in an array.
  """
  nin: [String!]
 
  """
  &&
  """
  overlap: [String!]
 
  """
  Regexp. Uses REGEXP operator
  """
  re: String
}

On this page