DEV Community

Cover image for Generating a Type-Safe Node.js API with Prisma, TypeGraphQL, and GraphQL-Query-Purifier
hellowwworld
hellowwworld

Posted on • Edited on

Generating a Type-Safe Node.js API with Prisma, TypeGraphQL, and GraphQL-Query-Purifier

This guide demonstrates how to quickly build a fully functional, type-safe Node.js API using Prisma, 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

1. Project Setup and Prisma Integration

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

Code for Project Setup:

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
Enter fullscreen mode Exit fullscreen mode

2. Defining Prisma Models

Create a Prisma schema with models representing a company structure, including sensitive salary data.

schema.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])
}
Enter fullscreen mode Exit fullscreen mode

Run migrations to create the database:

npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode
  1. Setting up Apollo Server with Express Integrate Apollo Server with Express, using TypeGraphQL for schema generation and resolvers.

Code for Server Setup:

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();

Enter fullscreen mode Exit fullscreen mode
  1. Testing the API Finally, let's test the API using GraphQL queries.

Legitimate Query:

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

Enter fullscreen mode Exit fullscreen mode

Malicious Query Attempt:

# Unauthorized attempt to access salaries
query {
  departments {
    id
    name
    employees {
      id
      name
      salary {
        amount
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Result

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

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

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.

Top comments (0)