- Introduction
- Overview
- Importance of Data Entities in Graphweaver
- Creating Data Entities
- Defining Data Entities for Different Data Providers
- Example: Creating a Database Entity for MySQL
- Example: Creating a Database Entity for PostgreSQL
- Example: Creating a REST Entity
- Example: Creating a Xero Entity
- Relationships
- Working with Data Entities (dataEntityForGraphQLEntity)
- Conclusion
Introduction
Data entities play a crucial role in Graphweaver, serving as the building blocks that define the structure and behaviour of data within the system.
In this article, we will explore the concept of data entities, their significance in Graphweaver, and how to create and customise them.
Overview
Data entities are representations of various data sources within Graphweaver. They act as the bridge between the underlying data providers and the GraphQL schema.
A data entity defines the shape of the data, including its properties and relationships.
By defining data entities, you establish a clear mapping between the data sources and the GraphQL schema. This allows you to seamlessly integrate and manipulate data from different sources in a unified manner.
Data entities may have more properties than you choose to expose via the GraphQL API, and it is this layer of abstraction that gives you a great deal of flexibility.
Importance of Data Entities in Graphweaver
Data entities are a part of the architecture principles of Graphweaver. Although it may seem like creating two entities each time is redundant it does offer a number of advantages:
- Abstraction of Data Sources: Data entities abstract away the complexities of interacting with various data providers. They provide a unified interface to access and manipulate data, regardless of the underlying source.
- Consistent GraphQL Schema: Data entities are “linked” to a GraphQL entity by mapping to specific types and fields. This allows you to decide which properties are exposed in the GraphQL schema.
- Data Manipulation and Validation: Data entities allow you to define validation rules, data transformations, and custom business logic within the schema layer. This ensures data integrity and consistency before interacting with the underlying data sources. An example would be a database constraint.
Creating Data Entities
To create a data entity in Graphweaver, define a class for it.
As of v1, it’s no longer necessary to extend the BaseEntity
class inside the @exogee/graphweaver-mikroorm
package. Check out the v0 to v1 migration guide for more.
Each data provider has its own set of decorators or configuration options for defining entity properties, relationships, and other provider-specific details.
Here's an example of creating a User
entity for a MySQL database for our built-in MikroORMProvider
:
import { BigIntType, Entity, PrimaryKey, Property } from '@mikro-orm/core';
@Entity()
export class User {
@PrimaryKey({ type: BigIntType })
id!: string;
@Property({ type: String })
firstName!: string;
@Property({ type: String })
lastName!: string;
}
Note that the above example is specific to a database entity.
Each data provider has its own set of decorators or configuration options for defining entity properties, relationships, and other provider-specific details.
Defining Data Entities for Different Data Providers
In this section, we'll explore how to define data entities for different data providers supported by Graphweaver. We'll provide examples for creating entities in MySQL, PostgreSQL, REST APIs, and Xero.
Example: Creating a Database Entity for MySQL
Below is a simple example of a user entity that is backed by a MySQL data source.
import { BigIntType, Entity, PrimaryKey, Property } from '@mikro-orm/core';
@Entity()
export class User {
@PrimaryKey({ type: BigIntType })
id!: string;
@Property({ type: String })
firstName!: string;
@Property({ type: String })
lastName!: string;
}
Example: Creating a Database Entity for PostgreSQL
Similar to the previous example, the below example is a data entity for PostgreSQL:
import { BigIntType, Entity, PrimaryKey, Property } from '@mikro-orm/core';
@Entity()
export class User {
@PrimaryKey({ type: BigIntType })
id!: string;
@Property({ type: String })
username!: string;
@Property({ type: String })
email!: string;
}
As you can see, the MySQL and PostgreSQL are identical!
Example: Creating a REST Entity
This example uses the Rest data provider:
import { Field } from '@exogee/graphweaver-rest';
export class User {
@Field()
name!: string;
@Field()
url!: string;
}
Example: Creating a Xero Entity
The Xero data provider is slightly different because it already knows the entity types available. So for this data provider you can simply import the xero-node
entity, like this:
import { Account, AccountType } from 'xero-node';
Relationships
While data entities provide a solid foundation for working with data in Graphweaver, you may need to customise them to meet your specific requirements.
One common customisation is adding relationships between entities to establish connections and enable efficient data querying.
To add relationships between data entities, you can leverage decorators or methods provided by Graphweaver Data Provider. These mechanisms allow you to define associations such as one-to-one, one-to-many, or many-to-many relationships.
By establishing relationships, you can retrieve related data in a structured and optimised manner.
Here's an example of adding a many-to-many relationship to the Task
entity:
import { BigIntType, Entity, PrimaryKey, Property, ManyToMany, Collection } from '@mikro-orm/core';
import { ExternalIdField } from '@exogee/graphweaver-mikroorm';
import { Tag } from './tag';
@Entity()
export class Task {
@PrimaryKey({ type: BigIntType })
id!: string;
@Property({ type: String })
description!: string;
@ExternalIdField({ from: 'user' })
@Property({ type: BigIntType })
userId!: string;
@ManyToMany(() => Tag, (tag) => tag.tasks, { owner: true })
tags: Collection<Tag> = new Collection<Tag>(this);
}
In the above example, we define a many-to-many relationship between the Task
entity and the Tag
entity. The @ManyToMany
decorator specifies the target entity and the mapping information.
This allows you to access all the tags associated with a task through the tags
property.
These two data entities are held in the same data source, so we can link them this way. However, sometimes you may only hold an ID to an external system. In the example above, you can see this is the case with the userId
field:
@ExternalIdField({ from: 'user' })
@Property({ type: BigIntType })
userId!: string;
By adding the ExternalIdField
decorator to this property, you are informing Graphweaver that this property is an ID to a data entity in another data source.
Working with Data Entities (dataEntityForGraphQLEntity
)
When writing custom queries, mutations, or fields on your GraphQL Entities, you may want to access the underlying data entity. To do this, use the dataEntityForGraphQLEntity
function.
Here’s an example snippet of this function in a custom field on a GraphQL Entity. In this example, we’re trying to access the userId
property of the data entity in order to create our user
field:
import { Entity, Field } from '@exogee/graphweaver';
import { MikroBackendProvider } from '@exogee/graphweaver-mikroorm';
import { myConnection } from '../database';
// OrmTask is the data entity type, aliased to avoid clashing names
import { Task as OrmTask } from '../entities';
import { User } from './user';
/*
This little example snippet is based on an example in the Graphweaver repository on Github
https://github.com/exogee-technology/graphweaver/tree/main/src/examples/databases
*/
@Entity('Task', {
provider: new MikroBackendProvider(OrmTask, myConnection),
})
export class Task {
...
// Normally, we'd use @RelationshipField decorator here instead of writing
// this logic manually. We're doing it this way for the example.
@Field(() => User)
async user(task: Task) {
const taskDataEntity = dataEntityForGraphQLEntity(
task as unknown as WithDataEntity<OrmTask>
);
const user = await BaseLoaders.loadOne({
gqlEntityType: Task,
id: taskDataEntity.userId,
});
return fromBackendEntity(User, user);
}
...
}
This example isn’t particularly fancy. We’re just reading off a property on the object, but from here we could use MikroORM to interact with data entities and the database directly, or we could access properties on the data entity that we don’t want to expose on the GraphQL API.
Right now, it’s necessary to cast to unknown
before casting to WithDataEntity<SomeDataEntityType>
.
WithDataEntity
tells Graphweaver that there is a data entity behind this GraphQL entity - not all GraphQL entities need to have data entities behind them!
Conclusion
Data entities serve as the building blocks of the Graphweaver system, providing a clear mapping between data sources and the GraphQL schema.
They play a crucial role in abstracting data sources, maintaining a consistent schema, and enabling data manipulation and validation.
By understanding the importance of data entities and learning how to create and customise them, you can leverage the full potential of Graphweaver and build sophisticated GraphQL APIs that integrate seamlessly with various data providers.