Creando una API con GraphQL y Apollo - Parte I
Tags: graphql, node, typescript, javascript
Published: 2019-01-25 17:54:24

> **Nota:** Este tutorial, por su complejidad, será divido en dos partes. La primera, que es el presente escrito, será dedicado al backend usando GraphQL y Apollo junto con TypeGraphQL. El segundo, al frontend usando la última versión de Angular.
> 

<p align="center">
<a href="https://github.com/gugadev/graphql-ng-signup">Source code</a>
</p>

En el [tutorial anterior](https://dev.to/gugadev/fundamentos-de-graphql-355k) aprendimos los conceptos básicos de GraphQL. Vimos qué eran las variables, las queries, mutations, entre otras cosas. Porque no es suficiente quedarse con la teoría, vamos a ponernos manos a la obra para practicar lo aprendido.

## Preparación del proyecto

Antes que nada, recuerda utilizar la última versión LTS de Node. Así mismo, puedes usar tanto NPM como Yarn.

La lista de paquetes que necesitamos instalar es:

- `@types/bcryptjs`
- `@types/graphql`
- `@types/lokijs`
- `@types/pino`
- `apollo-server`
- `bcryptjs`
- `class-validator`
- `graphql`
- `lokijs`
- `pino`
- `pino-pretty`
- `reflect-metadata`
- `type-graphql`
- `typedi`

Las dependencias de desarrollo son las siguientes:

- `@types/node`
- `nodemon`
- `ts-node`
- `tslint`
- `typescript`

Por último, agrega el script `start` para que corra nodemon ejecute ts-node y corra nuestra aplicación:

```json
{
  "scripts": {
    "start": "nodemon --exec ts-node src/main.ts --watch src/ --ignore src/database/data.json"
  }
}
```

## Creando los modelos

> Para enfocarnos en GraphQL vamos a usar una base de datos en memoria.

Lo primero será crear los modelos, en nuestro caso solo tenemos uno al cual llamaremos `User`:

```javascript
// user.type.ts
import {
  ObjectType,
  Field,
  Int
} from 'type-graphql'

@ObjectType()
export default class User {
  @Field(type => Int)
  id: number
  @Field()
  email: string
  @Field()
  password: string
}
```

Este tipo solo contiene tres campos:

- id: representa la PK.
- email
- password

Nota que `type-graphql` nos da tipos opcionales como `Int`cuando los tipos de JavaScript no nos son suficientes. Por ejemplo, por defecto, `number` es mapeado a un `Float` de GraphQL. Por esta razón, por medio del parámetro `type`, le decimos que es de tipo `INT`.

A su vez, esta misma clase será nuestro modelo con el que trabajará el motor de base de datos (siempre pensando en reutilizar 😉).

## Creando el servicio

Ahora procedemos a crear el servicio para `User`. Este se ve así:

```javascript
// user.service.ts
import { Service } from 'typedi'
import { hash, genSalt } from 'bcryptjs'
import db from '../database/client'
import User from './user.type'
import UserInput from './user.input'

@Service()
export default class UserService {
  private datasource = db.getCollection('users')

  findByEmail(email: strng): User {
    return this.datasource.findOne({ email })
  }
  async create(data: UserInput): Promise<User> {
    const body = {
      ...data,
      id: this.datsource.count() + 1,
      password: await hash(data.password, await genSalt(10))
    }
    const { id } = this.datasource.insert(body)
    return this.find(id)
  }
}

```

Lo primero a notar es que el servicio está anotado con el decorador `Service`. Este decorador nos permite registrar una clase como un servicio en el contenedor DI para posteriormente _inyectarlo_ en algún otro sitio.

El resto es realmente simple. Como propiedad tenemos `datasource`, la cual contiene la colleción `users` que hemos recuperado de la base de datos.

Finalmene tenemos dos métodos los cuales son `findByEmail` que encuentra un usuario por medio de su `email` y `create` el cual recibe un argumento de tipo `UserInput`, hashea su password plano, lo inserta en la colección y finalmente retorna el documento creado.

Suena bien, pero, ¿qué viene a ser `UserInput`? 🤔

## Argumentos personalizados

Recorarás que en el tutorial anterior hablamos de `input`, los cuales son tipos que engloban campos para ser pasados como un conjunto a través de un solo argumento en las consultas. Tomando este concepto, procedemos a crear nuestro propio input.

```javascript
import { IsEmail } from 'class-validator'
import {
  InputType,
  Field
} from 'type-graphql'

@InputType()
export default class UserInput {
  @Field()
  @IsEmail()
  email: string
  @Field()
  password: string
}

```

Te darás cuenta que es muy similar a `User`, ¿cierto? La única diferencia es la decoración `InputType`, por medio de la cual indicamos que esta clase es una estructura `input`. Además, como somos muy cuidadosos, validamos el campo `email` por medio de la decoración `isMail`, validación propiedad del paquete `class-validator` y que será automática, la misma que nos retornará un error a través de GraphQL si proveemos un valor erróneo para el campo.

## Creando el Resolver

Bien, hasta aquí ya tenemos los tipos, ahora procedamos a crear la consulta y la mutación con sus respectivos resolvers. Para esto, creamos una clase y la anotamos con `Resolver`, como muestro a continuación:

```javascript
import {
  Resolver,
  Arg,
  Query,
  Mutation,
  Int
} from 'type-graphql'
import User from './user.type'

@Resolver(of => User)
export default class UserResolver {
  
}
```

Por medio de la decoración `Resolver` indicamos que esta clase contendrá uno o más resolvers y además, por medio del argumento `of` le indicamos a quién pertenecerá; en este caso, a `User`. Ahora procedemos a incluir el servicio de `User` para consultar a la base de datos y retornar desde las consultas y mutaciones.

```javascript
// imports anteriores
import { Inject } from 'typedi'

@Resolver(of => User)
export default class UserResolver {
  @Inject() private service: UserService
}
```

Listo. Pero, ¿Qué ha pasado aquí? 🤔

La decoración `@Inject` "inyecta" la dependencia (una instancia) en una variable o argumento, dependencia que debe ser del mismo tipo que el de la variable. Cuando hacemos uso de `@Inject` lo que hacemos es decirle al contenedor:

> Hey, ¿Puedes buscar en tu registro si tienes una clase llamada ´
`UserService`? Si la tienes, necesito que me des una instancia y la guardes en la variable `service`.

¿Se entendió? Genial. Una vez que ya hemos incluido la dependencia de `UserService` ya estamos listos para usar sus métodos. Ahora, definamos nuestra `Query`. Esta se encargará de encontrar un usuario por medio de su `id`:

```javascript
// imports anteriores
import {
  ...
  Arg, // agregamos
  Query, // agregamos
  Int // agregamos
} from 'type-graphql'

@Resolver(of => User)
export default class UserResolver {
  ...
  @Query(returns => User, { nullable: true })
  user(@Arg('email') email: string) {
    return this.userService.findByEmail(email)
  }
}
```

Por medio del decorador `Query` indicamos que dicho método representa una consulta. Esta decoración acepta dos parámetros: el tipo de retorno y un array de opciones, el cual es opcional. Por medio de ese array le decimos que esta consulta **puede retornar null**, debido a que cuando un usuario no se encuentre, lo que será retornado será `null`. En caso contrario obtendríamos un error al retornar `null`.

En el argumento `id`, proveemos un decorador de tipo `Arg`, al cual le pasamos un nombre. Finalmente, cuando se ejecute el método, buscará en la base de datos ese `email` y retornará el usuario asociado.

La anterior definición se traduce al siguiente esquema GraphQL:

```javascript
type Query {
  user(email: String!): User
}
```

Sencillo, ¿no?. Ahora seguimos con nuestra mutación, la cual será encargada de crear un usuario en la base de datos. La definición del método es bastante similar a la consulta:

```javascript
// imports anteriores
import {
  ...
  Mutation // agregamos
} from 'type-graphql'
import UserInput from './user.input'

@Resolver(of => User)
export default class UserResolver {
  ...
  @Mutation(returns => User)
  user(@Arg('data') data: UserInput) {
    return this.userService.create(data)
  }
}
```

Fíjate en el argumento del método, ya no le pasamos el `type` en el decorador `Arg` porque ya lo hacemos por medio de Typescript. Lo que hará _type-graphql_ es usar Reflection para ver los tipos de los parámetros y hacer el mapeo correcto. ¡Es genial!

Lo anterior se traducirá a lo siguiente:

```javascript
type Mutation {
  createUser(data: UserInput!): User
}
```

## DI, Base de datos y Apollo

Ya tenemos casi todo lo que necesitamos, solo nos falta unos pequeños pasos. El primero es configurar nuestro container de inyección de dependencias. Para esto, hacemos lo siguiente:

```javascript
import { Container } from 'typedi'
import { useContainer } from 'type-graphql'

export default () => {
  useContainer(Container)
}
```

Importamos el container desde `typedi`y se lo pasamos a `type-graphql` para que lo configure por nosotros. Eso es todo lo que necesitamos hacer para tenerlo funcionando y poder proveer e inyectar dependencias.

Lo siguiente es crear nuestra bases de datos. Como dijimos al inicio del tutorial, será una base de datos en memoria, así que como es de suponer, el _setup_ será sencillísimo:

```javascript
// database/bootstrap.ts
import * as Loki from 'lokijs'

const db: Loki = new Loki('data.json')
db.addCollection('users')

export default db
```

Fíjate que en el momento en que instanciamos la base de datos, creamos una colección llamada `users`, que es donde se guardarán los usuarios que vayamos creando.

Finalmente, necesitamos crear nuestro servidor GraphQL usando Apollo. Veamos como luce:

```javascript
// server/index.ts
import { ApolloServer } from 'apollo-server'
import { buildSchema } from 'type-graphql'
import formatError from '../errors/argument.format'
import UserResolver from '../users/user.resolver'

/**
 * Creates a Apollo server using an
 * executable schema generated by
 * TypeGraphQL.
 * We provide a custom Apollo error
 * format to returns a non-bloated
 * response to the client.
 */
export default async () => {
  const schema = await buildSchema({
    resolvers: [
      UserResolver
    ]
  })
  return new ApolloServer({
    schema
  })
}
```

Lo primero que hacemos es importar los resolvers, luego pasárselos a `buildSchema` en forma de array para que nos genera un `schema` de GraphQL válido que pueda entender Apollo. Lo segundo, es instanciar `ApolloServer` y pasarle el `schema` junto con otras propiedades opcionales. Puedes ver la lista de propiedades [aquí](https://www.apollographql.com/docs/apollo-server/api/apollo-server.html). Una vez hecho esto, ya tenemos un servidor listo para correr.

## Entry point

Para terminar, creamos el archivo principal que pondrá a correr el servidor de Apollo. Para esto, solo importamos la función que crea el servidor y ejecutamos la función `listen`, la cual pondrá en escucha al servidor.

```javascript
// main.ts
import 'reflect-metadata'
import enableDI from './container/bootstrap'
import createServer from './server'
import log from './logger'

const run = async () => {
  enableDI()
  try {
    const server = await createServer()
    const { url } = await server.listen({ port: 3000 })
    log.info(`🚀  Server ready at ${url}`)
  } catch (e) {
    log.error(e)
  }
}

run()
```

## Opcional

### Error Formatter

Por defecto, cuando ocurre un error en tiempo de ejecución, GraphQL nos devuelve un gran objeto con muchos detalles, como en qué línea ocurrió, el stack trace, entre otras. Para no exponer demasiados detalles por seguridad y por simpleza, podemos crear un formateador que intercepte el error y lo modifique a nuestro antojo. Veamos un ejemplo:

```javascript
// errors/argument.format.ts
import { GraphQLError } from 'graphql'
import { ArgumentValidationError } from 'type-graphql'
import { ValidationError } from 'class-validator'

/**
 * Describes a custom GraphQL error format.
 * @param { err } Original GraphQL Error
 * @returns formatted error
 */
export default (err: GraphQLError): any => {
  const formattedError: { [key: string]: any } = {
    message: err.message
  }

  if (err.originalError instanceof ArgumentValidationError) {
    formattedError.validationErrors = err.originalError.validationErrors.map((ve: ValidationError) => {
      const constraints = { ...ve.constraints }
      return {
        property: ve.property,
        value: ve.value,
        constraints
      }
    })
  }
  return formattedError
}
```

Los formateadores de errores reciben un error de tipo `GraphQL`. Este error contiene propiedades como `message`, `paths`, `location`, `extensions`, entre otras. Sin embargo, podemos solo extrar lo que necesitemos. En este caso, solo necesitamos el mensaje y los errores de validación sin mucho detalle: solo la propiedad donde ocurrió el error, su valor y las restricciones que no pasó. De esta manera tenemos errores personalizados.

Para habilitarlo, solo se lo pasamos a la opción `formatError` del constructor de `ApolloServer`:

```javascript
return new ApolloServer({
    schema,
    formatError
  })
}
```

## Run, Forrest, Run!

Llegó la hora de la verdad. En este punto no hay marcha atrás: o corre o te disparas en la sien 😝 Para correr el servidor ejecuta el clásico `npm start`.

Si vamos a [localhost:3000](http://localhost:3000) veremos el Playground para empezar a jugar. ¡Ejecuta la consulta y mutación que aparece en la imagen para ver los resultados!

<p align="center">
  <img src="https://i.imgur.com/Nkaw21b.png" />
</p>

En la próxima entrega de esta serie veremos como consumir esta API desde Angular usando el cliente de Apollo. ¡Nos vemos! 🤘