
How to Build a GraphQL API with Prisma and Deploy to DigitalOcean App Platform
Building a GraphQL API with Prisma has become one of the most efficient ways to create type-safe, database-driven applications. When combined with DigitalOcean App Platform’s straightforward deployment process, you get a powerful stack that handles everything from database operations to production hosting. This guide walks you through creating a complete GraphQL API using Prisma as your ORM, setting up proper database connections, and deploying the whole thing to DigitalOcean App Platform with automatic builds and scaling.
How GraphQL with Prisma Works
GraphQL acts as a query language that lets clients request exactly the data they need, while Prisma serves as a modern ORM that generates type-safe database queries. The combination creates a robust API layer where Prisma handles database operations and GraphQL provides the interface for data fetching and mutations.
Prisma generates a client based on your database schema, providing auto-completion and type safety throughout your application. When you define your data models in the Prisma schema file, it creates the necessary database tables and generates TypeScript types that match your schema exactly.
The typical request flow works like this: GraphQL receives a query, resolvers process the request using Prisma client methods, Prisma translates these into optimized SQL queries, and the database returns results that flow back through the same chain to your client.
Project Setup and Initial Configuration
Start by creating a new Node.js project and installing the required dependencies. You’ll need GraphQL libraries, Prisma tooling, and a server framework like 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/node ts-node nodemon
Initialize Prisma in your project, which creates the necessary configuration files and a basic schema:
npx prisma init
This command creates a prisma
directory with 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 handle 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"]
}
Building the GraphQL Schema and Resolvers
Create your GraphQL schema that defines the API structure. This schema should align with your Prisma models but can expose different fields or add 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 use Prisma client to interact with your database. These functions handle the actual data fetching 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 }
});
}
}
};
Setting Up the Apollo Server
Create the main server file that combines your schema and resolvers with Apollo Server. This handles GraphQL query processing and provides 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 needed
return { req };
},
});
console.log(`π Server ready at ${url}`);
}
startServer().catch((error) => {
console.error('Error starting server:', error);
process.exit(1);
});
Add npm scripts to your package.json for development and production builds:
// package.json scripts section
{
"scripts": {
"dev": "nodemon --exec ts-node src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate dev",
"db:studio": "prisma studio"
}
}
Database Setup and Local Development
For local development, you can use a PostgreSQL database running in Docker or connect to a cloud database. Create a docker-compose.yml file for 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:
Update 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
Start your development server and test the API:
npm run dev
Visit http://localhost:4000
to access Apollo Studio where you can test your GraphQL queries and mutations.
Preparing for DigitalOcean App Platform Deployment
DigitalOcean App Platform requires specific configuration for successful deployment. Create an app specification file that defines your application structure:
// .do/app.yaml
name: graphql-prisma-api
services:
- environment_slug: node-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_nodes: 1
size: db-s-dev-database
version: "13"
Create a build script that handles 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": "node dist/server.js",
"postinstall": "npx prisma generate"
}
}
Deploying to DigitalOcean App Platform
Push your code to a Git repository and connect it to DigitalOcean App Platform. The platform supports automatic deployments from GitHub, GitLab, or direct Git repositories.
Create a new app on DigitalOcean App Platform through the control panel:
- Select “Create App” from the Apps section
- Choose your Git provider and repository
- Select the branch for deployment (typically main or master)
- Configure the build and run commands if not using app.yaml
- Add a managed PostgreSQL database or use an external connection
The platform automatically detects Node.js projects and suggests appropriate settings. Override these if you’re using custom configuration:
Setting | Development | Production |
---|---|---|
Build Command | npm run build | npm run build && npx prisma generate |
Run Command | npm run dev | npm start |
Environment | Node.js 18+ | Node.js 18+ (LTS) |
Instance Size | Basic | Basic or Professional |
Configure environment variables in the App Platform dashboard. The DATABASE_URL should point to your managed database or external PostgreSQL instance. DigitalOcean automatically provides the connection string for managed databases.
After deployment, run database migrations using the App Platform console or by including 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"
Performance Optimization and Best Practices
GraphQL with Prisma can generate complex queries that impact database performance. Implement query optimization strategies to maintain good response times:
// Implement connection limits and query depth limiting
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
requestDidStart() {
return {
willSendResponse(requestContext) {
// Log slow queries
if (requestContext.request.http?.headers?.get('x-response-time')) {
console.log('Query took:', requestContext.request.http.headers.get('x-response-time'));
}
},
};
},
},
],
});
Use Prisma’s built-in connection pooling and optimize database queries with proper indexing:
// Update schema.prisma for better 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 proper 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 fetching user:', error);
throw error;
}
}
Common Issues and Troubleshooting
Database connection issues are the most frequent problems 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"
Prisma client generation failures during build can occur when dependencies aren’t properly installed. Ensure your build command includes client generation:
// Common build issues and solutions
Problem: "PrismaClient is unable to be run in the browser"
Solution: Ensure prisma generate runs after npm install
Problem: "Environment variable not found: DATABASE_URL"
Solution: Check environment variable configuration in App Platform
Problem: "Migration failed during deployment"
Solution: Use prisma migrate deploy instead of prisma migrate dev
Memory issues can occur with large GraphQL queries. Implement 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)
],
});
Real-World Use Cases and Extensions
This GraphQL API setup works well for various application types. Blog platforms benefit from the post and user relationship structure, while e-commerce applications can extend the schema for 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 can be added 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('Invalid token');
}
}
return { user, prisma };
},
});
The combination of GraphQL, Prisma, and DigitalOcean App Platform provides a scalable foundation for modern web applications. The automatic scaling and managed database options make it suitable for applications ranging from small personal projects to enterprise-level systems.
For more advanced setups, consider implementing GraphQL subscriptions for real-time updates, using Prisma’s advanced features like aggregation and grouping, or integrating with DigitalOcean’s additional services like Spaces for file storage or Redis for caching.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or 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 upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.