DEV Community

Cover image for Building a Catalog GraphQL API with InversifyJS - Part 1: Project Setup & Type Generation
notaphplover
notaphplover

Posted on

Building a Catalog GraphQL API with InversifyJS - Part 1: Project Setup & Type Generation

Welcome to this series where we will build a complete Catalog GraphQL API using InversifyJS. In this first part, we will set up our project structure, configure TypeScript, and establish a build flow that automatically generates TypeScript types from our GraphQL schemas.

Overview

We are going to build a backend service that allows managing products and categories. We will use:

  • Node.js & TypeScript as our runtime and language.
  • InversifyJS for Dependency Injection.
  • GraphQL for our API query language.
  • Apollo Server to serve our GraphQL API.
  • Prisma (in later parts) for database access.

You can access the final implementation in our framework examples monorepo.

Step 1: Project Initialization

First, let's create a new directory for our project and initialize it. We will be setting this up as a standalone repository (not a monorepo).

mkdir catalog-graphql-api
cd catalog-graphql-api
pnpm init
Enter fullscreen mode Exit fullscreen mode

Now, let's install the necessary dependencies.

Runtime Dependencies

pnpm add inversify reflect-metadata graphql @apollo/server express @inversifyjs/http-express @inversifyjs/apollo-express @inversifyjs/http-core @inversifyjs/apollo-core dotenv
Enter fullscreen mode Exit fullscreen mode

Development Dependencies

We need TypeScript and some tools for our build process.

pnpm add -D typescript @types/node ts-node @inversifyjs/graphql-codegen rimraf prettier
Enter fullscreen mode Exit fullscreen mode

Configure ESM

We will be using ECMAScript Modules (ESM) for this project. Open your package.json and ensure you have "type": "module" set. This is important for top-level await support in our scripts.

{
  "name": "catalog-graphql-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: TypeScript Configuration

Create a tsconfig.json file in the root of your project. This configures how TypeScript compiles our code.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022"],
    "outDir": "./lib",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src"]
}
Enter fullscreen mode Exit fullscreen mode

Note: experimentalDecorators and emitDecoratorMetadata are crucial for InversifyJS to work correctly.

Step 3: Defining GraphQL Schemas

We will define our GraphQL schemas in .graphql files. This allows us to keep our schema definitions clean and separate from our code.

Create a directory structure graphql/schemas:

mkdir -p graphql/schemas
Enter fullscreen mode Exit fullscreen mode

Let's create a common schema file graphql/schemas/common.graphql for shared types:

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

interface Node {
  id: ID!
}
Enter fullscreen mode Exit fullscreen mode

And a main schema file graphql/schemas/schema.graphql. We'll add a simple query to start with:

# import * from "common.graphql"

type Query {
  hello: String
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Generating TypeScript Types

One of the best practices in GraphQL development is to generate TypeScript types from your schema. This ensures that your resolvers are type-safe and match your schema definition.

We will use @inversifyjs/graphql-codegen for this. Create a script file at src/scripts/generateGraphqlTypes.ts.

import { generateTsModels } from '@inversifyjs/graphql-codegen';
import * as prettier from 'prettier';

// Basic prettier config
const prettierConfig: prettier.Options = {
  parser: 'typescript',
  singleQuote: true,
  trailingComma: 'all',
};

await generateTsModels({
  destinationPath: './src/graphql/models/types.ts',
  pluginConfig: {
    avoidOptionals: false,
    enumsAsTypes: true,
    resolverTypeWrapperSignature: 'Partial<T> | Promise<Partial<T>>',
    useIndexSignature: false,
  },
  prettierConfig,
  schemas: {
    glob: {
      patterns: ['./graphql/schemas/*.graphql'],
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

This script does the following:

  1. Reads all .graphql files in graphql/schemas.
  2. Generates TypeScript interfaces and types.
  3. Saves them to src/graphql/models/types.ts.

Step 5: Configuring Build Scripts

Now, let's add some scripts to our package.json to run the generation and build the project.

Update the scripts section in package.json:

"scripts": {
  "build": "tsc && pnpm run generate:graphql:types",
  "build:clean": "rimraf lib",
  "generate:graphql:types": "ts-node ./src/scripts/generateGraphqlTypes.ts",
  "prebuild": "pnpm run build:clean"
}
Enter fullscreen mode Exit fullscreen mode

We use ts-node to run our generation script directly without compiling it first.

Step 6: Running the Build

Now you can run the build command:

pnpm run build
Enter fullscreen mode Exit fullscreen mode

If everything is set up correctly, you should see a new file generated at src/graphql/models/types.ts containing the TypeScript definitions for your GraphQL schema.

Conclusion

We have successfully set up our project structure and a build pipeline that ensures our TypeScript types are always in sync with our GraphQL schema.

In the next part, we will implement the InversifyJS container, set up the Apollo Server, and create our first resolvers.

Top comments (0)