Generating a Type-Safe Node.js API with Prisma, TypeGraphQL, and GraphQL-Query-Purifier
Tags: typegraphql, graphql, node, javascript
Published: 2023-11-18 18:10:32

This guide demonstrates how to quickly build a fully functional, type-safe Node.js API using Prisma, [typegraphql-prisma](https://www.npmjs.com/package/typegraphql-prisma), and graphql-query-purifier. We'll create a full CRUD API with partial access to protect sensitive data, even with autogenerated resolvers.

> Full example is available in [repo](https://github.com/multipliedtwice/graphql-query-purifier-example)

## 1. Project Setup and Prisma Integration

First, create a new Node.js project and integrate Prisma for database management.

Code for Project Setup:

```bash
mkdir nodejs-api && cd nodejs-api
npm init -y
npm i --save-dev @types/cors@2.8.16 @types/graphql-fields@1.3.9 @types/node@20.9.2 body-parser@1.20.2 cors@2.8.5 express@4.18.2 graphql-query-purifier prisma@5.6.0 ts-node@10.9.1 type-graphql@2.0.0-beta.1 typegraphql-prisma@0.27.1 typescript@5.2.2
npm i @apollo/server@4.9.5 @prisma/client@5.6.0 graphql@16.8.1 graphql-fields@2.0.3 graphql-scalars@1.22.4 reflect-metadata@0.1.13
npx tsc --init
npx prisma init --datasource-provider sqlite
```

## 2. Defining Prisma Models
Create a Prisma schema with models representing a company structure, including sensitive salary data.

schema.prisma:

```prisma
// schema.prisma

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

generator typegraphql {
  provider = "typegraphql-prisma"
  output   = "generated"
}

model Employee {
  id           Int        @id @default(autoincrement())
  name         String
  departmentId Int
  department   Department @relation(fields: [departmentId], references: [id])
  salary       Salary?
  salaryId     Int?
}

model Department {
  id        Int        @id @default(autoincrement())
  name      String
  employees Employee[]
}

model Salary {
  id         Int      @id @default(autoincrement())
  amount     Float
  employeeId Int      @unique
  employee   Employee @relation(fields: [employeeId], references: [id])
}
```
Run migrations to create the database:

```bash
npx prisma migrate dev --name init
```

3. Setting up Apollo Server with Express
Integrate Apollo Server with Express, using TypeGraphQL for schema generation and resolvers.

Code for Server Setup:

```javascript
import "reflect-metadata";
import express from "express";
import { ApolloServer } from "@apollo/server";
import { GraphQLQueryPurifier } from "graphql-query-purifier";
import { resolvers } from "../prisma/generated";
import { PrismaClient } from "@prisma/client";

import cors from "cors";
import path from "path";
import { json, urlencoded } from "body-parser";
import { expressMiddleware } from "@apollo/server/express4";
import { buildSchema } from "type-graphql";

const startServer = async () => {
  const app = express();
  const prisma = new PrismaClient();

  app.use(cors(), json(), urlencoded({ extended: true }));

  const gqlPath = path.resolve(__dirname, "../frontend");
  const queryPurifier = new GraphQLQueryPurifier({
    gqlPath,
    allowStudio: true,
    // allowAll: false,
  });
  app.use(queryPurifier.filter);

  const server = new ApolloServer({
    schema: await buildSchema({
      resolvers,
      validate: false,
    }),
  });

  await server.start();

  const context = expressMiddleware(server, {
    context: async (_ctx) => ({
      prisma,
    }),
  });

  app.use("/graphql", context);

  const PORT = process.env.PORT || 3000;
  app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}/graphql`);
  });
};

startServer();

```

5. Testing the API
Finally, let's test the API using GraphQL queries.

### Legitimate Query:

```graphql
# Fetch departments and their employees
query {
  departments {
    id
    name
    employees {
      id
      name
    }
  }
}

```


### Malicious Query Attempt:

```graphql
# Unauthorized attempt to access salaries
query {
  departments {
    id
    name
    employees {
      id
      name
      salary {
        amount
      }
    }
  }
}
```

![Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kmqexii9nnmuezzwn4ux.png)

As you might've guessed - this query will show only data that is explicitly allowed to be shown by our `.gql` files. 

## Conclusion

This guide covered creating a Node.js API with TypeGraphQL, graphql-query-purifier, and architecture. The main takeaways:

**TypeGraphQL for Auto-Generated Schema and Resolvers:**

- Efficiency: TypeGraphQL and Prisma produce database schema-based GraphQL resolvers. This accelerates the building of a GraphQL API with full CRUD capabilities.
- Type Safety: Strong API typing. A type-safe environment with TypeGraphQL and TypeScript reduces type-related problems and improves code quality.

**Improved Security: [graphql-query-purifier](https://www.npmjs.com/package/graphql-query-purifier)**

GraphQL query-purifier filters incoming queries to prevent data leaks. Avoiding over-fetching and unauthorized access to sensitive data is crucial for APIs with autogenerated resolvers.

Our API protects sensitive data like employee pay. The graphql-query-purifier protects such data from unauthorized queries, which is crucial for compliance and privacy.

### Practical Results:

Combining these tools creates a robust, secure, and scalable API. It handles complicated data structures and relationships securely.

![Autogenerated resolvers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9uulngau16jwx19pg4i.png)

**Less Development Time and Effort:** GraphQL schema and resolver auto-generation and security features reduce boilerplate code and increase business logic emphasis.

Modularity and cutting-edge tools prepare the API for future scalability and maintainability issues.

This API development method speeds up creation and adds security and type safety. It shows a fast and secure way to build modern web apps.