# 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](https://apollographql.com) 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](https://typegraphql.ml) 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.

{% hint style="info" %}
`sequelize.sync({ force: true })` will automatically create the required tables for you.
{% endhint %}

{% hint style="danger" %}
Don't forget to change it to `force: false` when you got live.
{% endhint %}

{% code title="001\_example.ts" %}

```typescript
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
  }
};
```

{% endcode %}

### 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.

{% code title="\~/packages/api/src" %}

```
$ mv models entities
```

{% endcode %}

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](https://typegraphql.ml/docs/installation.html), we have to import `reflect-metadata` before we use/import `type-graphql` or our resolvers.

Update `Link.ts`to add `ObjectType` and `Field`.

{% code title="entities/Link.ts" %}

```typescript
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 };
```

{% endcode %}

Create a resolver in a new folder `GraphQL` in`routers` 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

{% tabs %}
{% tab title="resolvers/Link.ts" %}

```typescript
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 };
```

{% endtab %}

{% tab title="resolvers.ts" %}

```typescript
export * from "./Link";
```

{% endtab %}
{% endtabs %}

Our new `GraphQL` router will build the schema

{% code title="routers/GraphQL/index.ts" %}

```typescript
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: "/" });
  }
}
```

{% endcode %}

Don't forget to add it to `app.ts`

{% code title="app.ts" %}

```typescript
export const createApp = () => {
  const app = express();
  // ...
  app.use("/graphql", new GraphQLRouter().router);
  return { app };
};
```

{% endcode %}

We are now able to send GraphQL queries. Let's open <http://localhost:3000/graphql> and see if it works.

![It works!](/files/-LnmzE-nJYOnMZyFFxwA)

### Return data from the database

Update the containers with `Link`

{% code title="container.ts" %}

```typescript
export const setLinkEntity = (entity: typeof Link) => Container.set(ContainerNames.linkEntity, entity)
export const getLinkEntity = (): typeof Link => Container.get(ContainerNames.linkEntity)
```

{% endcode %}

then set our `Link` entity when we declare it

{% code title="entities/Link.ts" %}

```typescript
// ...
import { setLinkEntity } from "../containers";

// ...
class Link extends Model<Link> {
 // ...
}

setLinkEntity(Link);

export { Link };
```

{% endcode %}

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 be `Link`.
* `total` the amount of items available to query.
* `hasMore` will be true if you can request more items.

{% code title="PaginatedResponse.ts" %}

```typescript
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;
}
```

{% endcode %}

The second one, `PaginatedArgs.ts`, adds query arguments:

* `limit` how many items to retrieve.
* `offset` how many items to skip.

{% code title="PaginatedArgs.ts" %}

```typescript
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 };
```

{% endcode %}

{% hint style="info" %}
We have to set `"declaration": false` in `tsconfig.json`.
{% endhint %}

Let's modify the `Link`resolver to use it:

{% code title="resolvers/Link.ts" %}

```typescript
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 };
```

{% endcode %}

### Writing integration tests

We can use `createTestClient` from [ApolloTesting](https://www.apollographql.com/docs/apollo-server/testing/testing/).

Add the requires dependencies:

{% code title="\~/packages/api" %}

```
$ yarn add apollo-server-core apollo-server-testing
```

{% endcode %}

* Create a server
* Build the schema with the resolver
* Send the query

```typescript
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.

{% hint style="info" %}
[`graphql`](https://github.com/florianherrengt/book-code/tree/graphql) branch available on GitHub.
{% endhint %}

{% code title="resolvers/Link.ts" %}

```typescript
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 };
```

{% endcode %}

{% hint style="info" %}
[`graphql`](https://github.com/florianherrengt/book-code/tree/graphql) branch available on GitHub.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tutorial.specian.co.uk/backend/graphql.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
