Friends Of AdonisFriends Of Adonis

Getting Started

Installation

Install and configure the package using the following command :

node ace add @foadonis/graphql

Getting Started

To explore the different capabilities of GraphQL we will create a sample GraphQL API for cooking recipes.

Let's start tih the Recipe type, which is the foundation of our API.

We assume that you already now Adonis and its ORM Lucid. The migrations have been omitted from the tutorial.

Create our Recipe type

The Recipe

We will start with a basic Recipe model.

app/models/recipe.ts
import { BaseModel, column } from "@adonisjs/lucid/orm";
import { DateTime } from "luxon";
 
export default class Recipe extends BaseModel {
  @column({ isPrimary: true })
  declare id: string;
 
  @column()
  declare title: string;
 
  @column()
  declare description: string | null;
 
  @column()
  declare ingredients: string[];
}

The ObjectType

We now need to create an object type from our model. It will then be used in our GraphQL schema.

app/models/recipe.ts
import { BaseModel, column } from "@adonisjs/lucid/orm";
import { ObjectType, Field } from "@foadonis/graphql/decorators"; 
import { DateTime } from "luxon";
 
@ObjectType() 
export default class Recipe extends BaseModel {
  @column({ isPrimary: true })
  @Field((type) => ID) 
  declare id: string;
 
  @column()
  @Field() 
  declare title: string;
 
  @column()
  @Field({ nullable: true }) 
  declare description: string | null;
 
  @column()
  @Field(() => [String]) 
  declare ingredients: string[];
}

For simplicity here we use directly our model in our schema, but you can create a type from any class.

Create the CRUD operations

After that we want to create typical crud operations. Also called queries and mutations. For this we have to create a Resolver (similar to a controller).

The Resolver

app/graphql/recipe_resolver.ts
import Recipe from "#models/recipe";
import { Arg, Args, Int, Mutation, Query, Resolver } from "type-graphql";
 
@Resolver(Recipe)
export default class RecipeResolver {
  @Query(() => Recipe)
  recipe(@Arg("id") id: number) {
    return Recipe.findOrFail(id);
  }
 
  @Query(() => [Recipe])
  recipes(@Args() { page, perPage }: RecipeArgs) {
    return Recipe.query().paginate(page, perPage);
  }
 
  @Mutation(() => Recipe)
  addRecipe(@Arg("newRecipeData") newRecipeData: NewRecipeInput) {
    return Recipe.create(newRecipeData);
  }
 
  @Mutation(() => Boolean)
  async removeRecipe(@Arg("id") id: number) {
    const recipe = await Recipe.find(id);
 
    if (!recipe) {
      return false;
    }
 
    await recipe.delete();
    return true;
  }
}

The Inputs and Arguments

We are missing two important pieces, the inputs for the recipes query and the arguments for the addRecipe mutation. Let's create them:

app/graphql/recipe_resolver.ts
import { ArrayMaxSize, Length, Max, MaxLength, Min } from "class-validator";
import { ArgsType, Field, InputType, Int } from "type-graphql";
 
@ArgsType()
class RecipeArgs {
  @Field(() => Int)
  @Min(0)
  page: number = 0;
 
  @Field(() => Int)
  @Min(1)
  @Max(25)
  perPage: number = 0;
}
 
@InputType()
class NewRecipeInput {
  @Field()
  @MaxLength(30)
  declare title: string;
 
  @Field({ nullable: true })
  @Length(30, 255)
  declare description?: string;
 
  @Field(() => [String])
  @ArrayMaxSize(30)
  declare ingredients: string[];
}

The @Length, @Min and @ArrayMaxSize are decorators coming from class-validator a well-known validation library that rely on decorators.

Register the Resolver

We must now register our Resolver with the GraphQL server:

start/graphql.ts
import graphql from "@foadonis/graphql/services/main";
 
graphql.resolvers([() => import("#graphql/resolvers/recipe_resolver")]);
Only the root resolvers must be registered this way.

Access the Playground

Everything is now ready!

You can access the GraphQL playground at http://localhost:3000/graphql and start playing with your queries and mutations.

GraphQL Playground

On this page