Chapter 29 - Crafting Harmonious APIs: The Symphony of TypeScript and GraphQL

Crafting a Harmonious Symphony: Elevating API Development with TypeScript and GraphQL's Dynamic Duo.

Chapter 29 - Crafting Harmonious APIs: The Symphony of TypeScript and GraphQL

Imagine you’re setting out on a journey to build an API that doesn’t just work but sings in harmony with the rest of your application. This is where the fusion of TypeScript and GraphQL come into play. Picture creating a system so robust, errors are caught before they even think about causing you a headache. This duo not only fortifies your API but makes it graceful under the pressure of scaling.

Starting your project feels like opening a fresh page of possibilities. The first step is like setting the foundation for a masterpiece. You’ll informally start by making a space on your computer for this new project. Get comfy with a bit of terminal magic:

mkdir graphql-ts-example
cd graphql-ts-example
npm init -y

It’s like stocking up on essentials for a cooking adventure, except here you’ll be installing the ingredients for your project to flourish. GraphQL and Express take the stage, and TypeScript joins as the guide, making sure everything speaks the same language.

npm install graphql express express-graphql
npm install -D nodemon ts-node @types/express typescript

With TypeGraphQL, the process of creating APIs becomes a song instead of a cacophony. Think of class-validator, type-graphql, and reflect-metadata as the conductors ensuring everything stays in key.

npm install class-validator type-graphql reflect-metadata

Now, configuring TypeScript is akin to setting your workshop just right. You craft a tsconfig.json file to ensure every tool has its place:

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "lib": ["es2018", "esnext.asynciterable"],
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  },
  "exclude": ["node_modules"]
}

These settings are like a warm embrace for TypeScript, enabling all the clever behind-the-scenes tricks it can muster, especially when it comes to decorators.

Next, you prep your scripts like you’re tuning a radio, ensuring you hit just the right frequency for your server to broadcast smoothly. This involves dialing into your package.json with a couple of scripts:

"scripts": {
  "start": "nodemon --exec ts-node src/index.ts",
  "start:dev": "nodemon --watch src --exec ts-node src/index.ts"
}

These scripts are your trusty companions, ready to restart the show whenever you tweak the performance.

Now, onto the star of the show: your GraphQL schema, the script of your API play. Using TypeGraphQL feels like writing dialogue for your characters. Create a cozy users directory, and within it, a users.schema.ts file unfolds:

import { Field, ObjectType, InputType } from "type-graphql";

@ObjectType()
export class User {
  @Field()
  id!: number;

  @Field()
  name!: string;

  @Field()
  email!: string;
}

@InputType()
export class UserInput implements Pick<User, "name" | "email"> {
  @Field()
  name!: string;

  @Field()
  email!: string;
}

In essence, you’ve crafted personas — User and UserInput — adorned with TypeScript’s assurances, promising they’ll behave as expected on the GraphQL stage.

Resolvers are like directors shouting “action!” These functions enable your server to fetch the necessary data for each role in the script. In users.resolvers.ts, the magic happens:

import { Resolver, Query, Mutation, Arg } from "type-graphql";
import { User } from "./users.schema";
import { UserInput } from "./users.schema";

@Resolver()
export class UserResolver {
  @Query(() => [User])
  async users(): Promise<User[]> {
    const users = [
      { id: 1, name: "John Doe", email: "[email protected]" },
      { id: 2, name: "Jane Doe", email: "[email protected]" },
    ];
    return users;
  }

  @Mutation(() => User)
  async createUser(@Arg("input") userInput: UserInput): Promise<User> {
    const newUser = { id: 3, name: userInput.name, email: userInput.email };
    return newUser;
  }
}

These resolvers bring life to the script, orchestrating data fetches and updates with grace and precision.

Lastly, it’s time to raise the curtain with your Express server. Create an index.ts within the src directory, setting the stage for your API’s debut:

import express from "express";
import { graphqlHTTP } from "express-graphql";
import { buildSchema } from "graphql";
import { UserResolver } from "./users/users.resolvers";
import { User } from "./users/users.schema";

const app = express();

const schema = buildSchema(`
  type Query {
    users: [User]
  }

  type Mutation {
    createUser(input: UserInput): User
  }

  type User {
    id: Int!
    name: String!
    email: String!
  }

  input UserInput {
    name: String!
    email: String!
  }
`);

const resolvers = {
  Query: {
    users: UserResolver.prototype.users,
  },
  Mutation: {
    createUser: UserResolver.prototype.createUser,
  },
};

app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: resolvers,
    graphiql: true,
  })
);

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server is running on localhost:${PORT}`);
});

Your Express server knows its script, ready to deliver every line with aplomb. The graphqlHTTP middleware is like a seasoned stage manager, ensuring everything happens right on cue.

To kick off the performance, run your trusted command, like a stage manager ushering everyone into position:

npm run start:dev

Voila! Your server rises, ready to impress with its seamless back-and-forth of queries and data responses.

The joy of using TypeScript with GraphQL isn’t just in the type safety. It’s in the peace of mind knowing everything aligns perfectly, like a symphony where every musician hits the right note at the right time. Tools like GraphQL Code Generator further tighten the symphony, automatically composing TypeScript definitions from your GraphQL schema, ensuring your API performs with elegance:

import { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: '<URL_OF_YOUR_GRAPHQL_API>',
  documents: ['src/**/*.{ts,tsx}'],
  generates: {
    './src/__generated__/': {
      preset: 'client',
      plugins: [],
      presetConfig: {
        gqlTagName: 'gql',
      },
    },
  },
  ignoreNoDocuments: true,
};

export default config;

Running the code generator feels like final adjustments before the curtain call, ensuring every detail harmonizes:

yarn run compile

In the end, TypeScript and GraphQL isn’t just a partnership; it’s a creative union that allows for structure and imagination to coexist beautifully. By embracing this dance, the API development turns into a masterpiece — robust, maintainable, and capable of wowing anyone who comes to witness its performance.