GraphQL Intro
Zox.js was created with one goal in mind: Making scalable systems
And this can easily be achieved using the graphql-plugins
.
Setup a new zox project with GraphQL support:
npm i zox zox-plugins graphql-plugins
Lets start by defining a data type. Create a file called User.ts
// GraphQL type:
export const UserDef = `
type User
{
id: ID!
name: String
subscribedTo: [User]
}
`;
// TypeScript types corresponding to the database tables:
export type User = {
id: number
name: string
};
export type UserSubscription = {
userId: number
subscribedToUserId: number
};
// TypeScript type corresponding to our output
export type UserData = {
id: number
name: string
subscribedTo: Array<UserData>
};
In our example GraphQL will expect the User type to have a list of subscriptions, but in our database this is stored in a separate table.
So we will have to write a resolver to fetch this additional data.
We will then also have to define a top level query field, to make the user data available.
In the following example we will use mocked versions of our users and subscriptions tables.
Create a file called UserResolvers.ts
import {Query, Resolver, ResolverBase} from "graphql-plugins";
import {UserData, User, UserSubscription, UserDef} from "./User";
import {users, subscriptions} from "./MockUserData";
@Resolver('User', 'subscribedTo', UserDef)
export class UsersQuery extends ResolverBase
{
public resolve(thisUser, args, context): Array<UserData>
{
return subscriptions
// filter only subscriptions of this user
.filter(s => s.userId == thisUser.id)
// load users to which this user is subscribed to
.map(s => users.find(u => u.id == s.subscribedToUserId));
}
}
@Query('user(id: ID!): User', UserDef)
export class UserQuery extends ResolverBase
{
public resolve(root, args, context): Array<UserData>
{
return users.find(u => u.id == args.id);
}
}
Here we define a Resolver
for the subscribedTo
field of the User
type
where we define how to load the contents for this field.
Then we define a top level Query
field called user
that takes one required argument id
which we use to try to find the requested data.
In both cases we referenced the GraphQL type definition UserDef
,
which will be used by graphql-plugins
to generate the schema.
It is important to reference all of the type definitions
related to each of our resolvers,
in order to make sure they are all included in the final schema.
Here's how a request to our new GraphQL schema might look like:
{
user(id: 5) {
id
name
subscribedTo {
id
name
}
}
}
Or if you want to make the id
parameter dynamic:
query($id: ID!){
user(id: $id) {
id
name
subscribedTo {
id
name
}
}
}
We could also exclude the subscribedTo
field from the query:
query($id: ID!){
user(id: $id) {
id
name
}
}
In which case the resolver for the subscribedTo
field will not execute
and we will avoid executing an unnecessary query.
The examples here are very simplified. In production you will want to use a data loader to combine all your queries into one or two, in order to avoid executing tens or hundreds of queries per request.
Context
Zox.js does not provide the context
variable by default.
Instead you are expected to set the function for generating the context from your dev.js script.
container.get(IGraphQLService).contextGenerator = (request) =>
{
return { /* ... */ };
};