Loading Now

How to Build a GraphQL API with Prisma and Deploy to DigitalOcean App Platform

A Complete Guide to Constructing a GraphQL API with Prisma and Deploying on DigitalOcean App Platform

Creating a GraphQL API leveraging Prisma is one of the most effective methods for developing type-safe, database-oriented applications. When paired with the user-friendly deployment features of DigitalOcean App Platform, this combination yields a robust technology stack adept at managing everything from database tasks to live hosting. In this guide, we will explore the steps necessary to establish a full GraphQL API using Prisma as your ORM, ensure correct database connections, and deploy the entire setup to the DigitalOcean App Platform with automated builds and scaling.

How Prisma Integrates with GraphQL

GraphQL functions as a query language, allowing clients to request precisely the data they require, while Prisma operates as a modern ORM, efficiently crafting type-safe database queries. This synergy produces a strong API framework where Prisma manages database tasks and GraphQL offers the data interaction interface.

Prisma creates a client reflecting your database schema, providing autocompletion and type safety across your application. When you outline your data models in the Prisma schema file, it establishes the necessary database tables and generates corresponding TypeScript types that align with your schema.

The standard process for requests operates as follows: GraphQL receives a query, resolvers manage the request utilising Prisma client methods, Prisma converts these into optimised SQL commands, and the database sends back results that return through the same pathway to your client.

Initial Setup and Configuration

Begin by creating a new de.js project and adding the essential dependencies. You will require GraphQL libraries, Prisma tools, and a server framework such as Apollo server.

mkdir graphql-prisma-api

cd graphql-prisma-api
npm init -y

npm install @apollo/server graphql prisma @prisma/client
npm install -D typescript @types/de ts-de demon

Initialise Prisma within your project, which will generate the necessary configuration files and a foundational schema:

npx prisma init

This command produces a prisma directory containing a schema.prisma file and adds a .env file for your database connection string. Update your schema file with a sample data model:

// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Create a TypeScript configuration file to manage module resolution and compilation:

// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

Creating the GraphQL Schema and Resolvers

Define your GraphQL schema that specifies the structure of the API. This schema should correlate with your Prisma models but can exhibit alternative fields or additional computed properties:

// src/schema.ts

export const typeDefs = `
type User {
id: Int!
email: String!
name: String
posts: [Post!]!
createdAt: String!
updatedAt: String!
}

type Post {
id: Int!
title: String!
content: String
published: Boolean!
author: User!
createdAt: String!
updatedAt: String!
}

type Query {
users: [User!]!
user(id: Int!): User
posts: [Post!]!
post(id: Int!): Post
publishedPosts: [Post!]!
}

type Mutation {
createUser(email: String!, name: String): User!
createPost(title: String!, content: String, authorId: Int!): Post!
publishPost(id: Int!): Post
deletePost(id: Int!): Post
}
`;

Implement resolvers that utilise the Prisma client to interact with your database. These functions manage the actual data retrieval and manipulation:

// src/resolvers.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export const resolvers = {
Query: {
users: async () => {
return await prisma.user.findMany({
include: { posts: true }
});
},

user: async (_: any, { id }: { id: number }) => {
return await prisma.user.findUnique({
where: { id },
include: { posts: true }
});
},

posts: async () => {
return await prisma.post.findMany({
include: { author: true }
});
},

post: async (_: any, { id }: { id: number }) => {
return await prisma.post.findUnique({
where: { id },
include: { author: true }
});
},

publishedPosts: async () => {
return await prisma.post.findMany({
where: { published: true },
include: { author: true }
});
}

},

Mutation: {
createUser: async (_: any, { email, name }: { email: string; name?: string }) => {
return await prisma.user.create({
data: { email, name },
include: { posts: true }
});
},

createPost: async (_: any, { title, content, authorId }: { title: string; content?: string; authorId: number }) => {
return await prisma.post.create({
data: { title, content, authorId },
include: { author: true }
});
},

publishPost: async (_: any, { id }: { id: number }) => {
return await prisma.post.update({
where: { id },
data: { published: true },
include: { author: true }
});
},

deletePost: async (_: any, { id }: { id: number }) => {
return await prisma.post.delete({
where: { id },
include: { author: true }
});
}

}
};

Configuring the Apollo server

Craft the primary server file that melds your schema and resolvers with Apollo server. This manages the GraphQL query processing and offers the GraphQL Playground for development:

// src/server.ts

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';

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

const { url } = await startStandaloneServer(server, {
listen: { port: parseInt(process.env.PORT || '4000') },
context: async ({ req }) => {
// Add authentication logic here if applicable
return { req };
},
});

console.log(🚀 server running at ${url});
}

startServer().catch((error) => {
console.error('Failed to start server:', error);
process.exit(1);
});

Incorporate npm scripts in your package.json for development and production builds:

// package.json scripts section
{
"scripts": {
"dev": "demon --exec ts-de src/server.ts",
"build": "tsc",
"start": "de dist/server.js",
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate dev",
"db:studio": "prisma studio"
}
}

Database Setup for Local Development

For local development, you can employ a PostgreSQL database via Docker or connect to a cloud database. Create a docker-compose.yml file to set up local PostgreSQL:

// docker-compose.yml

version: '3.8'
services:
postgres:
image: postgres:15
restart: always
environment:
POSTGRES_DB: graphql_api
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:

  • "5432:5432"
    volumes:
  • postgres_data:/var/lib/postgresql/data

volumes:
postgres_data:

Modify your .env file with the database connection string:

# .env
DATABASE_URL="postgresql://postgres:password@localhost:5432/graphql_api"
PORT=4000

Generate the Prisma client and push your schema to the database:

docker-compose up -d
npx prisma generate
npx prisma db push

Initiate your development server and test the API:

npm run dev

Navigate to http://localhost:4000 to access Apollo Studio and experiment with your GraphQL queries and mutations.

Preparing for Deployment on DigitalOcean App Platform

DigitalOcean App Platform necessitates specific configurations for a smooth deployment. Create an app specification file that delineates your application structure:

// .do/app.yaml

name: graphql-prisma-api
services:

  • environment_slug: de-js
    github:
    branch: main
    deploy_on_push: true
    repo: your-username/your-repo-name
    name: api
    routes:

    • path: /
      run_command: npm start
      build_command: npm run build && npx prisma generate
      instance_count: 1
      instance_size_slug: basic-xxs
      source_dir: /
      envs:
    • key: DATABASE_URL
      scope: RUN_TIME
      type: SECRET
    • key: PORT
      scope: RUN_TIME
      value: "8080"
      databases:
  • engine: PG
    name: main-db
    num_des: 1
    size: db-s-dev-database
    version: "13"

    Create a build script to manage Prisma client generation during deployment:

    // scripts/build.sh
    #!/bin/bash
    set -e

echo "Installing dependencies..."
npm ci

echo "Building TypeScript..."
npm run build

echo "Generating Prisma client..."
npx prisma generate

echo "Build completed successfully!"

Make the script executable and update your package.json:

chmod +x scripts/build.sh

// Update package.json
{
"scripts": {
"build": "tsc && npx prisma generate",
"start": "de dist/server.js",
"postinstall": "npx prisma generate"
}
}

Deploying on DigitalOcean App Platform

Upload your code to a Git repository and link it to DigitalOcean App Platform. This platform facilitates automatic deployments from GitHub, GitLab, or direct Git repositories.

To create a new application on DigitalOcean App Platform through the control panel:

  • Select “Create App” from the Apps section
  • Pick your Git provider and repository
  • Select the branch for deployment (generally main or master)
  • Configure build and run commands in accordance with your app.yaml file
  • Add a managed PostgreSQL database or utilise an external connection

The platform will automatically recognise de.js projects and propose the necessary settings. Override these if you’re operating with custom configurations:

Setting Development Production
Build Command npm run build npm run build && npx prisma generate
Run Command npm run dev npm start
Environment de.js 18+ de.js 18+ (LTS)
Instance Size Basic Basic or Professional

Set environment variables in the App Platform dashboard. The DATABASE_URL should point to your managed database or external PostgreSQL instance. DigitalOcean will automatically provide the connection string for managed databases.

Once deployed, run database migrations using the App Platform console or include migration commands in your build process:

// For initial deployment, run migrations manually

npx prisma migrate deploy

// Or include in build process
"build": "tsc && npx prisma generate && npx prisma migrate deploy"

Optimising Performance and Best Practices

Utilising GraphQL with Prisma may result in complex queries that could negatively affect database performance. It's crucial to employ strategies for query optimisation to ensure satisfactory response times:

// Implement connection limits and restrict query depth

const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
requestDidStart() {
return {
willSendResponse(requestContext) {
// Log queries that take a long time
if (requestContext.request.http?.headers?.get('x-response-time')) {
console.log('Query duration:', requestContext.request.http.headers.get('x-response-time'));
}
},
};
},
},
],
});

Leverage Prisma's built-in connection pooling and enhance database queries through proper indexing:

// Update schema.prisma for enhanced performance
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)

@@index([createdAt])
}

model Post {
id Int @id @default(autoincrement())
title String @db.VarChar(255)
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)

@@index([published, createdAt])
@@index([authorId])
}

Implement appropriate error handling and logging for production environments:

// src/utils/errors.ts
export class GraphQLError extends Error {
extensions: Record;

constructor(message: string, code: string, statusCode: number = 400) {
super(message);
this.extensions = {
code,
http: { status: statusCode }
};
}
}

// In resolvers
user: async (_: any, { id }: { id: number }) => {
try {
const user = await prisma.user.findUnique({
where: { id },
include: { posts: true }
});

if (!user) {
throw new GraphQLError('User not found', 'USER_NOT_FOUND', 404);
}

return user;

} catch (error) {
console.error('Error retrieving user:', error);
throw error;
}
}

Troubleshooting Common Issues

Database connection problems are some of the most prevalent issues faced during deployment. DigitalOcean App Platform requires specific SSL configurations for PostgreSQL connections:

# Update DATABASE_URL for DigitalOcean managed databases

DATABASE_URL="postgresql://username:password@host:port/database?sslmode=require"

Failures in Prisma client generation during the build can occur if dependencies are not installed correctly. Ensure your build command includes client generation:

// Common build issues and solutions
Problem: "PrismaClient cannot run in the browser"
Solution: Ensure prisma generate runs post npm install

Problem: "Environment variable not found: DATABASE_URL"
Solution: Verify environment variable settings in App Platform

Problem: "Migration failure during deployment"
Solution: Use prisma migrate deploy instead of prisma migrate dev

Memory issues might arise with large GraphQL queries. Consider implementing query complexity analysis and request size limits:

npm install graphql-query-complexity graphql-depth-limit

// Add to server configuration
import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-query-complexity';

const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(10),
createComplexityLimitRule(1000)
],
});

Potential Use Cases and Extensions

This GraphQL API framework is suitable for a variety of application types. Blog platforms benefit from the established relationship between posts and users, while e-commerce applications can expand the schema to include products and orders:

// E-commerce extension example

model Product {
id Int @id @default(autoincrement())
name String
description String?
price Decimal @db.Decimal(10, 2)
inventory Int @default(0)
orders OrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model Order {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
items OrderItem[]
total Decimal @db.Decimal(10, 2)
status OrderStatus @default(PENDING)
createdAt DateTime @default(now())
}

Authentication and authorization features can be integrated using JWT tokens and context-based access control:

// JWT authentication middleware
import jwt from 'jsonwebtoken';

const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
let user = null;

if (req.headers.authorization) {
const token = req.headers.authorization.replace('Bearer ', '');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;
user = await prisma.user.findUnique({ where: { id: decoded.userId } });
} catch (error) {
console.warn('Token is invalid');
}
}

return { user, prisma };

},
});

The union of GraphQL, Prisma, and DigitalOcean App Platform creates a scalable base for contemporary web applications. Automatic scaling and managed database options render it suitable for projects ranging from modest personal initiatives to comprehensive enterprise systems.

For more sophisticated configurations, think about incorporating GraphQL subscriptions for real-time updates, using Prisma's advanced functionalities like aggregation and grouping, or integrating with DigitalOcean’s additional services, such as Spaces for file storage or Redis for caching.



This article includes references and materials from multiple online sources. We acknowledge and appreciate the contributions from all original authors, publishers, and websites. While every effort has been made to credit the source material accurately, any unintentional omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes your copyright, please contact us immediately for review and appropriate action.

This article is intended solely for informational and educational purposes and does not infringe upon the rights of copyright owners. If any copyrighted material has been used without proper attribution or in violation of copyright laws, it was unintentional, and we will correct it promptly upon notification. Please note that the redistribution or reproduction of any portion or all of the contents in any form is prohibited without the express written permission from the author and website owner. For permissions or further inquiries, please reach out to us.