Mikro ORM
WARNING
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 install @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.
Generating GraphQL Operations from MikroORM's Entity Schema
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 wire bobbin from the Entity Schema.
import { EntitySchema } from "@mikro-orm/core"
import { MikroOperationBobbin, 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 giraffeBobbin = new MikroOperationBobbin(Giraffe, () => orm.em)
In the code above, we first define a Giraffe
entity and then create a bobbin using MikroOperationBobbin
. From the bobbin, we can generate GraphQL operations directly.
const GiraffeResolver = resolver.of(Giraffe, {
giraffe: giraffeBobbin.FindOneQuery(),
giraffes: giraffeBobbin.FindManyQuery(),
createGiraffe: giraffeBobbin.CreateMutation(),
updateGiraffe: giraffeBobbin.UpdateMutation(),
deleteGiraffe: giraffeBobbin.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
}