GAZAR

Principal Engineer | Mentor

Building a GraphQL Server with Apollo Server, Prisma, and TypeScript

Building a GraphQL Server with Apollo Server, Prisma, and TypeScript

In recent years, GraphQL has gained immense popularity as a query language for APIs due to its flexibility and efficiency. Apollo Server is a GraphQL server implementation that simplifies the process of building GraphQL APIs, while Prisma offers a powerful ORM (Object-Relational Mapping) for database interactions. When combined with TypeScript, these tools provide a robust foundation for building scalable and type-safe GraphQL servers. In this article, we'll explore how to integrate Apollo Server with Prisma using TypeScript to create a GraphQL API.

mkdir apollo-prisma-server
cd apollo-prisma-server
npm init -y
npm install apollo-server graphql prisma @prisma/client typescript ts-node @types/node

Next, initialize TypeScript in the project:

npx tsc --init

Prisma simplifies database access by generating type-safe database client libraries based on your data model. Let's define our data model in Prisma. Create a new file called schema.prisma in the project root:

// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  posts     Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

Now, generate the Prisma client:

npx prisma generate

Setting Up Apollo Server:

Create a new file called index.ts in the project root to define our Apollo Server:

import * as yup from 'yup';
const createUserSchema = yup.object().shape({
  name: yup.string().required(),
  email: yup.string().email().required(),
});

const updateUserSchema = yup.object().shape({
  id: yup.number().required(),
  name: yup.string(),
  email: yup.string().email(),
});

const createPostSchema = yup.object().shape({
  title: yup.string().required(),
  content: yup.string().required(),
  authorId: yup.number().required(),
});

const updatePostSchema = yup.object().shape({
  id: yup.number().required(),
  title: yup.string(),
  content: yup.string(),
});

Next, let's update our resolver functions to perform input validation before executing mutations:

import { ValidationError } from 'yup';
const resolvers = {
  Mutation: {
    createUser: async (_, { data }) => {
      try {
        await createUserSchema.validate(data, { abortEarly: false });
        return prisma.user.create({ data });
      } catch (error) {
        if (error instanceof ValidationError) {
          throw new Error(error.errors.join(', '));
        }
        throw error;
      }
    },
    updateUser: async (_, { data }) => {
      try {
        await updateUserSchema.validate(data, { abortEarly: false });
        const { id, ...rest } = data;
        return prisma.user.update({
          where: { id },
          data: rest,
        });
      } catch (error) {
        if (error instanceof ValidationError) {
          throw new Error(error.errors.join(', '));
        }
        throw error;
      }
    },
    createPost: async (_, { data }) => {
      try {
        await createPostSchema.validate(data, { abortEarly: false });
        return prisma.post.create({ data });
      } catch (error) {
        if (error instanceof ValidationError) {
          throw new Error(error.errors.join(', '));
        }
        throw error;
      }
    },
    updatePost: async (_, { data }) => {
      try {
        await updatePostSchema.validate(data, { abortEarly: false });
        const { id, ...rest } = data;
        return prisma.post.update({
          where: { id },
          data: rest,
        });
      } catch (error) {
        if (error instanceof ValidationError) {
          throw new Error(error.errors.join(', '));
        }
        throw error;
      }
    },
  },
};

Then you can have the server

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen().then(({ url }) => {
  console.log(`Server running at ${url}`);
});

This code defines our GraphQL schema, resolvers, and sets up an Apollo Server instance. We're using the Prisma client to interact with the database.

Now, let's start our Apollo Server:

npx ts-node index.ts

Your GraphQL server should now be running at the specified URL (typically http://localhost:4000). You can access the GraphQL Playground to interact with your API and execute queries.

Conclusion:

In this article, we've seen how to build a GraphQL server with Apollo Server, Prisma, and TypeScript. By leveraging the power of GraphQL for API queries, Prisma for database interactions, and TypeScript for type safety, we've created a robust and scalable backend for our applications. This setup allows for efficient development and maintenance of GraphQL APIs with confidence in type safety and data integrity.