DEV Community

Cover image for GraphQL: Create your API using TypeScript and decorators with Rakkit
owen for daven

Posted on • Edited on

GraphQL: Create your API using TypeScript and decorators with Rakkit

What?

Okay then, this is related to my previous article about Rakkit. So I'll advise you to go take a look around ๐Ÿ˜Š.

So, here I will show you a more concrete example of what you can do using Rakkit to create a GraphQL API with a user management system.

But first: the installation of Rakkit ๐Ÿ’พ

So there are few dependencies that we must install to continue:

Here, I would use apollo-server but it's also possible to install apollo-server-koa if you use Rakkit for REST and GraphQL which allows you to link contexts.

Just run this command to install the required dependencies:

npm i rakkit graphql @types/graphql apollo-server reflect-metadata
Enter fullscreen mode Exit fullscreen mode

reflect-metadata allows us to use the decorators with TypeScript

Okay cool, now we just need to configure TypeScript to enable the decorators by creating a tsconfig.json file at the root of the project, containing this:

{
  "compileOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "commonjs",
    "target": "es2016",
    "noImplicitAny": false,
    "sourceMap": true,
    "outDir": "build",
    "declaration": true,
    "importHelpers": true,
    "forceConsistentCasingInFileNames": true,
    "lib": [
      "es2016",
      "esnext.asyncitable"
    ],
    "moduleResolution": "node"
  }
}
Enter fullscreen mode Exit fullscreen mode

./tsconfig.json

The definitions of types ๐Ÿšป

Okay then let's start by creating our User class, which we'll have to decorate with @ObjectType():

import { ObjectType, Field } from "rakkit";
import * as Crypto from "crypto";

@ObjectType()
export class User {
  @Field()
  username: string;

  @Field()
  email: string;

  @Field()
  id: string;

  // Just to show a computed property:
  @Field(type => String)
  get flatInfos(): string {
    return [this.name, this.email, this.id].join(":");
  }

  constructor(username: string, email: string) {
    this.username = username;
    this.email = email;
    this.id = Crypto.randomBytes(16).toString("hex");
  }
}
Enter fullscreen mode Exit fullscreen mode

./types/User.ts

You need a small "database" ๐Ÿ—‚

So we're going to have to play with some users in order to test our app, so I'm just going to create a list of user instances to make it clearer:

You can use a real database with an ORM like TypeORM for your projects

import { User } from "../types/User";

export const users = [
  new User("JohnDoe", "john@doe.com"),
  new User("JaneDoe", "jane@doe.com"),
  new User("Ben", "ben@doe.com")
];
Enter fullscreen mode Exit fullscreen mode

./db/users.ts

Resolver (Query, Mutation, Subscription) ๐Ÿš€

It is in the following class that we will define our query/mutation/subscription. It will contain a simple CRUD and a subscription to be notified when a user is registered:

import {
  Resolve,
  Query,
  Mutation,
  Subscription,
  IContext,
  Arg
} from "rakkit";
import { User } from "../types/User";
import { users } from "../db/users";

@Resolver()
export class UserResolver {
  @Query(returns => [User])
  getAllUsers() { {
    return users;
  }

  @Query({ nullable: true })
  getOneUserByName(@Arg("name") name: string): User {
    return users.find((user) => user.name ==== name);
  }

  @Mutation()
  addUser(
    // Defining the mutation arguments
    @Arg("name") name: string,
    @Arg("email") email: string,
    context: IContext
  ): User {
    const user = new User(name, email);
    users.push(user);
    // Publish the event for subscriptions with the created user
    context.gql.pubSub.publish("USER_ADDED", user);
    return user;
  }

  @Subscription({ topics: "USER_ADDED" })
  userAddedNotif(createdUser: User): User {
    // Send the created user to the client
    return createdUser;
  }
}
Enter fullscreen mode Exit fullscreen mode

./resolvers/UserResolver.ts

The point of entry ๐Ÿšช

Now we need to have an entry point for our application:

// It allows us to use decorators:
import "reflect-metadata";

import { Rakkit } from "rakkit";
import { ApolloServer } from "apollo-server";

async function bootstrap() {
  await Rakkit.start({
    gql: {
      // You give an array of glob string:
      resolvers: [`${__dirname}/resolvers/*Resolver.ts`]
    }
  });
  // Retrieve the GraphQL compiled schema:
  const schema = Rakkit.MetadataStorage.Gql.Schema;

  const server = new ApolloServer({
    schema
  });

  server.listen();
}

bootstrap();
Enter fullscreen mode Exit fullscreen mode

./bootstrap.ts

Done, so let's start and test it ! ๐ŸŽ‰

To start it you must install ts-node globally to run directly your TypeScript app:

npm i -g ts-node
Enter fullscreen mode Exit fullscreen mode

Then just run this:

ts-node relative-path-to/bootstrap.ts
Enter fullscreen mode Exit fullscreen mode

And just go to http://localhost:4000 with your favorite browser to make some GraphQL queries! ๐Ÿ”ฅ

getAllUsers - Get all users:

getOneUserByName - Get a specific user by name:

addUser - Add an user:

userAddedNotif - Listen to the user creation event:

Et voilร ! This example is available on GitHub ๐Ÿ˜Š, thanks!

Top comments (3)

Collapse
 
marcus-sa profile image
Marcus S. Abildskov • Edited

Oh wow, this looks just like an exact copy of @nestjs/graphql just without the big community to back it up.. lol

Collapse
 
owen profile image
owen

Yes the GraphQL package looks like type-graphql (used by nest) but there is several differences that are mentioned in the docs ๐Ÿ™‚
For the community, every projects start from zero, we are building it ๐Ÿ˜‰

Collapse
 
marcus-sa profile image
Marcus S. Abildskov • Edited

Well, IMO reinventing the wheel is never good :)
And especially in this case it makes absolutely no sense at all.
What would you rather use? A new framework which reinvents everything (not even better), or a battle production tested framework with several community backed packages?