GraphQL
There are many advantages to GraphQL but predictability is one of the best.
Send a GraphQL query to your API and get exactly what you need, nothing more and nothing less. GraphQL queries always return predictable results.
Apollo is one of the most popular libraries to build a GraphQL API today. It works well with Node and React. We will be using it. We will also use TypeGraphQL to define our schema with TypeScript classes.
From now, we are building our app. So let's remove the migration until we need it. We'll keep one for reference.
Don't forget to change it to force: false
when you got live.
import { QueryInterface } from "sequelize";
module.exports = {
// change the database schema
async up(query: QueryInterface) {
// add migration here
},
// revert in case it goes wrong
async down(query: QueryInterface) {
// revert
}
};
Move from models to entities
The vast majority of GraphQL queries and mutations are to interact with the database. To avoid repeating ourselves, we will define your models and GraphQL schema with the same files.
Rename models
to entities
to reflect the semantic of our files.
$ mv models entities
Change sequelize.ts
import path import * as models from "./entities";
.
Build the GraphQL Schema
Install dependencies
$ yarn add graphql @types/graphql type-graphql apollo-server-express
Following the TypeGraphQL documentation, we have to import reflect-metadata
before we use/import type-graphql
or our resolvers.
Update Link.ts
to add ObjectType
and Field
.
import 'reflect-metadata';
import { Model, Column, Table, DataType } from "sequelize-typescript";
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
@Table({
tableName: "link",
comment: "Links saved by a user"
})
class Link extends Model<Link> {
@Field(() => ID)
@Column({
primaryKey: true,
allowNull: true,
defaultValue: DataType.UUIDV4
})
id: string;
@Field()
@Column({
allowNull: false
})
uri: string;
}
export { Link };
Create a resolver in a new folder GraphQL
inrouters
as well as resolvers
to keep things tidy.
.
├── GraphQL
  ├── index.ts
  └── resolvers
  ├── Link.ts
  └── index.ts
For now, we're just going to return a hardcoded array of Links
import { Resolver, Query } from "type-graphql";
import { Link } from "../../../entities";
@Resolver(of => Link)
class LinkResolver {
@Query(returns => [Link])
links() {
return [{ id: "123", uri: "http://test.com" }];
}
}
export { LinkResolver };
Our new GraphQL
router will build the schema
import "reflect-metadata";
import { ApolloServer } from "apollo-server-express";
import { Router } from "express";
import { buildSchema } from "type-graphql";
import * as resolvers from "./resolvers";
export class GraphQLRouter {
public readonly router = Router();
constructor() {
this.buildSchema();
}
async buildSchema() {
const schema = await buildSchema({
resolvers: Object.values(resolvers)
});
const apolloServer = new ApolloServer({
schema
});
apolloServer.applyMiddleware({ app: this.router, path: "/" });
}
}
Don't forget to add it to app.ts
export const createApp = () => {
const app = express();
// ...
app.use("/graphql", new GraphQLRouter().router);
return { app };
};
We are now able to send GraphQL queries. Let's open http://localhost:3000/graphql and see if it works.

Return data from the database
Update the containers with Link
export const setLinkEntity = (entity: typeof Link) => Container.set(ContainerNames.linkEntity, entity)
export const getLinkEntity = (): typeof Link => Container.get(ContainerNames.linkEntity)
then set our Link
entity when we declare it
// ...
import { setLinkEntity } from "../containers";
// ...
class Link extends Model<Link> {
// ...
}
setLinkEntity(Link);
export { Link };
and use it in our resolver
Pagination
Results should always be paginated for performance reasons. It's good practice to add it at the beginning.
We are going to create 2 helpers in routers/GrapqhQL
.
The first one, PaginatedResponse.ts
, generate a new class with to attributes:
items
the data returned. In this case, it will beLink
.total
the amount of items available to query.hasMore
will be true if you can request more items.
import { ClassType, ObjectType, Field, Int } from "type-graphql";
export function PaginatedResponse<TItem>(TItemClass: ClassType<TItem>) {
// `isAbstract` decorator option is mandatory to prevent registering in schema
@ObjectType({ isAbstract: true })
abstract class PaginatedResponseClass {
// here we use the runtime argument
@Field(type => [TItemClass])
// and here the generic type
items: TItem[];
@Field(type => Int)
total: number;
@Field(type => Boolean)
hasMore: Boolean;
}
return PaginatedResponseClass;
}
The second one, PaginatedArgs.ts
, adds query arguments:
limit
how many items to retrieve.offset
how many items to skip.
import { ArgsType, Field, Int } from "type-graphql";
@ArgsType()
class PaginatedArgs {
@Field(type => Int, { nullable: true })
offset?: number;
@Field(type => Int, { nullable: true })
limit?: number;
}
export { PaginatedArgs };
Let's modify the Link
resolver to use it:
import { Resolver, Query, ObjectType, Args } from "type-graphql";
import { Link } from "../../../entities";
import { getLinkEntity } from "../../../containers";
import { PaginatedResponse } from "../helpers";
import { PaginatedArgs } from "../helpers/PaginatedArgs";
@ObjectType()
class PaginatedLink extends PaginatedResponse(Link) { }
@Resolver(of => PaginatedLink)
class LinkResolver {
private readonly _LinkEntity = getLinkEntity();
@Query(returns => PaginatedLink)
async links(@Args() { limit = 20, offset = 0 }: PaginatedArgs): Promise<PaginatedLink> {
const { rows: items, count: total } = await this._LinkEntity.findAndCountAll({
limit,
offset,
order: [['createdAt', 'DESC']]
});
return { items, total, hasMore: offset + items.length <= total };
}
}
export { LinkResolver };
Writing integration tests
We can use createTestClient
from ApolloTesting.
Add the requires dependencies:
$ yarn add apollo-server-core apollo-server-testing
Create a server
Build the schema with the resolver
Send the query
import { ApolloServerBase, gql } from "apollo-server-core";
import {
createTestClient,
ApolloServerTestClient
} from "apollo-server-testing";
import { buildSchema } from "type-graphql";
import { LinkResolver } from "./Link";
import { setLinkEntity } from "../../../containers";
import { Link } from "../../../entities";
describe("routers/GraphQL/Resolvers/Link", () => {
let testClient: ApolloServerTestClient;
beforeAll(async () => {
// create a graphql schema with the resolver
const schema = await buildSchema({
resolvers: [LinkResolver]
});
// create a server to send requests to
const server = new ApolloServerBase({ schema });
// use the test client to send queries and mutations
// more info https://www.apollographql.com/docs/apollo-server/testing/testing
testClient = createTestClient(server);
});
describe("query/links", () => {
it("should return the list of links in the database", async () => {
// mock sequelize
const findAndCountAll = jest.fn();
const fakeLink = { id: "1", uri: "http://test" };
findAndCountAll.mockResolvedValue({ rows: [fakeLink], count: 1 });
setLinkEntity(({ findAndCountAll } as unknown) as typeof Link);
// send the query
const { data, errors } = await testClient.query({
query: gql`
{
links {
items {
id
uri
}
total
}
}
`
});
// assert results
expect(errors).toBeUndefined();
expect(data!.links.items).toHaveLength(1);
expect(data!.links.items[0]).toMatchObject(fakeLink);
expect(data!.links.hasMore).toBeFalsy();
});
});
});
Conclusion
We have seen how to:
Retrieve and create rows in the database
Paginate the results
Write isolated integration tests for the resolvers
The next step is now to add user authentication and make sure only owners can access their links.
import { Resolver, Query } from "type-graphql";
import { Link } from "../../../entities";
import { getLinkEntity } from "../../../containers";
@Resolver(of => Link)
class LinkResolver {
private readonly _LinkEntity = getLinkEntity()
@Query(returns => [Link])
async links(): Promise<Link[]> {
const links = this._LinkEntity.findAll()
return links;
}
}
export { LinkResolver };
Last updated
Was this helpful?