Integrate Prisma with Strapi
Integrate Prisma with Strapi using a microservices architecture where each tool excels independently. Run Prisma as a separate service for type-safe transactional operations and complex business logic while Strapi handles content management through its native admin panel and APIs
These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is Prisma?
Prisma is an open-source, type-safe ORM specifically designed for Node.js and TypeScript developers. It consists of three core components: Prisma Client (auto-generated query builder), Prisma Migrate (declarative migration tool), and Prisma Schema (data modeling language).
The platform supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, and MongoDB through a consistent API. Note that Strapi 5 does not officially support Prisma as a database layer replacement—Prisma must operate as an independent service with separate database schemas.
Why Integrate Prisma with Strapi
The reality is you can have both tools, but not the way you'd expect. If you've worked with Strapi's query layer, you know it gets the job done, but you might miss Prisma's type safety. If you're coming from Prisma, you might want Strapi's admin panel without giving up your ORM.
Prisma can be integrated at the ORM level inside a Strapi project, using a shared Prisma schema and database rather than requiring independent services or separate schemas. This complementary architecture is often used in microservices patterns where your frontend consumes both APIs separately, but it is not limited to that style of architecture.
Within these architectural constraints, here are four technical benefits:
Type Safety: Prisma auto-generates TypeScript types from your database schema, catching type mismatches at compile time rather than runtime. This provides fully type-safe database access where every query, relation, and field is validated before your code runs.
Declarative Migration Management: Prisma Migrate uses a declarative workflow where you define your desired database state in schema.prisma, and Prisma generates SQL migration files automatically. This provides predictable schema evolution with version-controlled migration history that can be reviewed in pull requests.
Modern Developer Experience: Prisma prioritizes developer experience through intuitive schema syntax, comprehensive IDE integration with VS Code for syntax highlighting and auto-completion, and visual database management tools like Prisma Studio.
Multi-Database Support: Prisma supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, MongoDB, and CockroachDB through a single, consistent API. This enables database switching with minimal code changes and different databases per environment.
When This Architecture Makes Sense
- Hybrid applications that need content management via Strapi plus complex business logic via Prisma.
- Type-safe backend development where TypeScript-first teams require compile-time safety.
- Database feature requirements where you need database-specific features not exposed through Strapi's abstraction.
- Multi-database architectures using different databases for different purposes.
How to Integrate Prisma with Strapi
Production experience shows that trying to make these tools share a database causes more problems than it solves. This isn't a simple npm install situation—it requires significant architectural planning. Here's the approach that actually works in production.
This implementation runs Prisma alongside Strapi as a completely independent service through a microservices architecture. Prisma manages custom data models for application-specific business logic while Strapi's Knex.js ORM handles core content types, admin panel, and CMS functionality. They operate on separate database schemas or instances to prevent conflicts, with the frontend consuming both APIs independently.
Prerequisites
This works with Node.js 16 or higher with npm/yarn, a supported database (PostgreSQL, MySQL, MariaDB, or SQLite), and an existing Strapi 5 project. If you don't have Strapi installed yet, create one using:
npx create-strapi@latest my-strapi-projectThis sets up a Strapi 5 project with SQLite for local development.
Setting Up Prisma in Your Project
Add Prisma to your existing Strapi project using a microservices architecture with separate database schemas:
npm install prisma --save-dev
npm install @prisma/clientGetting Prisma Ready
Set up your Prisma configuration:
npx prisma init --datasource-provider postgresqlThis creates a prisma directory with your schema and environment files.
TypeScript Configuration for Both Systems
Configure your tsconfig.json to work with Strapi's TypeScript setup:
{
"extends": "@strapi/typescript-utils/tsconfigs/server",
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
},
"include": [
"./",
"src/**/*.ts",
"src/**/*.d.ts"
],
"exclude": [
"node_modules/",
"build/",
"dist/",
".cache/",
".tmp/",
"src/admin/",
"**/*.test.ts",
"src/plugins/**"
]
}Create config/typescript.ts to enable Strapi's TypeScript features:
export default { autogenerate: true };This auto-generates types from your Strapi schema.
Defining Your Prisma Schema
Critical Configuration: The output parameter in your Prisma schema ensures compatibility with Strapi's build process.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
output = "../node_modules/@prisma/client"
}
model CustomData {
id Int @id @default(autoincrement())
name String
value String?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Generate Prisma Client:
npx prisma generateConnecting to Separate Databases
The separation requires distinct database configurations in your .env file:
# Strapi Database Configuration
NODE_ENV=production
DATABASE_CLIENT=postgres
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=strapi_db
DATABASE_USERNAME=strapi_user
DATABASE_PASSWORD=secure_password
DATABASE_SSL=false
# Prisma Database Configuration (separate schema)
DATABASE_URL="postgresql://prisma_user:password@localhost:5432/prisma_db?schema=prisma"This isolation prevents migration conflicts and schema management issues between the two systems. Use secure secret-management mechanisms (such as environment variables, encrypted configuration files, or dedicated secret managers/vaults) for database credentials in production, and never hardcode them in source code.
Managing Prisma Client Connections
Create src/prismaClient.ts to manage the Prisma Client instance:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient({
log: [
{
emit: 'stdout',
level: 'error',
},
{
emit: 'stdout',
level: 'query',
},
{
emit: 'stdout',
level: 'info',
},
{
emit: 'stdout',
level: 'warn',
},
],
});
export default prisma;Enabling query logging in development helps diagnose performance issues early.
Building Type-Safe Services
Create a custom service using Strapi's Document Service API or Query Engine API for your custom data models. Strapi's architecture indicates that custom services should leverage Strapi's built-in Knex.js-based data access layer rather than external ORMs.
If you require Prisma for application-specific business logic outside of Strapi's content management, implement it as a separate microservice with its own database schema. Save your Strapi service as src/api/custom-data/services/custom-data.ts:
import { PrismaClient, CustomData, Prisma } from '@prisma/client';
import prisma from '../../../prismaClient';
interface CustomDataService {
findMany(): Promise<CustomData[]>;
findOne(id: number): Promise<CustomData | null>;
create(data: Prisma.CustomDataCreateInput): Promise<CustomData>;
update(id: number, data: Prisma.CustomDataUpdateInput): Promise<CustomData>;
delete(id: number): Promise<CustomData>;
}
const customDataService: CustomDataService = {
async findMany() {
return await prisma.customData.findMany({
orderBy: { createdAt: 'desc' }
});
},
async findOne(id: number) {
return await prisma.customData.findUnique({
where: { id }
});
},
async create(data: Prisma.CustomDataCreateInput) {
return await prisma.customData.create({ data });
},
async update(id: number, data: Prisma.CustomDataUpdateInput) {
return await prisma.customData.update({
where: { id },
data
});
},
async delete(id: number) {
return await prisma.customData.delete({
where: { id }
});
}
};
export default customDataService;While Prisma's auto-generated types do provide compile-time type safety, integrating Prisma directly into a Strapi service layer is not officially supported. Strapi 5 uses Knex.js exclusively as its ORM layer, and there is no official integration pattern for third-party ORMs like Prisma. Any type-safe database access using Prisma would require a separate microservices architecture where Prisma operates independently from Strapi's Knex.js-based data layer.
Creating Your Controllers
Set up the controller that exposes your Prisma service through Strapi's backend customization framework. Save as src/api/custom-data/controllers/custom-data.ts:
import { Context } from 'koa';
import { Prisma } from '@prisma/client';
export default {
async find(ctx: Context) {
try {
const data = await prisma.customData.findMany({
orderBy: { createdAt: 'desc' }
});
ctx.body = data;
} catch (error) {
ctx.throw(500, 'Error fetching data');
}
},
async findOne(ctx: Context) {
try {
const id = parseInt(ctx.params.id);
if (isNaN(id)) {
ctx.throw(400, 'Invalid ID');
}
const data = await strapi
.service('api::custom-data.custom-data')
.findOne(id);
if (!data) {
ctx.throw(404, 'Data not found');
}
ctx.body = data;
} catch (error) {
ctx.throw(error.status || 500, error.message);
}
},
async create(ctx: Context) {
try {
const createData: Prisma.CustomDataCreateInput = ctx.request.body;
const data = await strapi
.service('api::custom-data.custom-data')
.create(createData);
ctx.body = data;
} catch (error) {
ctx.throw(500, 'Error creating data');
}
},
async update(ctx: Context) {
try {
const id = parseInt(ctx.params.id);
if (isNaN(id)) {
ctx.throw(400, 'Invalid ID');
}
const updateData: Prisma.CustomDataUpdateInput = ctx.request.body;
const data = await strapi
.service('api::custom-data.custom-data')
.update(id, updateData);
ctx.body = data;
} catch (error) {
ctx.throw(500, 'Error updating data');
}
},
async delete(ctx: Context) {
try {
const id = parseInt(ctx.params.id);
if (isNaN(id)) {
ctx.throw(400, 'Invalid ID');
}
const data = await prisma.customData.delete({
where: { id }
});
ctx.body = data;
} catch (error) {
ctx.throw(error.status || 500, error.message || 'Error deleting data');
}
}
};Setting Up Routes
Define the routes that expose your custom endpoints. Create src/api/custom-data/routes/custom-data.ts:
export default {
routes: [
{
method: 'GET',
path: '/custom-data',
handler: 'custom-data.find',
config: {
policies: [],
},
},
{
method: 'GET',
path: '/custom-data/:id',
handler: 'custom-data.findOne',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/custom-data',
handler: 'custom-data.create',
config: {
policies: [],
},
},
{
method: 'PUT',
path: '/custom-data/:id',
handler: 'custom-data.update',
config: {
policies: [],
},
},
{
method: 'DELETE',
path: '/custom-data/:id',
handler: 'custom-data.delete',
config: {
policies: [],
},
},
],
};Handling Connection Lifecycle
Add lifecycle hooks to properly initialize and close Prisma Client connections. Update src/index.ts:
import prisma from './prismaClient';
module.exports = {
register({ strapi }) {
// Registration logic
},
async bootstrap({ strapi }) {
console.log('Prisma Client initialized');
},
async destroy({ strapi }) {
await prisma.$disconnect();
console.log('Prisma Client disconnected');
},
};The lifecycle hooks in src/index.ts manage connection lifecycle, ensuring database connections are properly closed when the application shuts down. The destroy hook is called during application termination, making it the appropriate place to disconnect the Prisma Client using prisma.$disconnect() to gracefully close all database connections and release resources.
Managing Migrations in Both Systems
This is where the architecture presents challenges—you're essentially running two migration systems. It's manageable if you treat each system as completely independent.
Handle Prisma migrations and Strapi's migration system separately from each other:
# Development: Create migration
npx prisma migrate dev --name init
# Production: Apply migrations
npx prisma migrate deploy
# Regenerate Prisma Client after schema changes
npx prisma generateWhen to use this command: After making any changes to your prisma/schema.prisma file, you must run npx prisma generate to regenerate the Prisma Client with updated types and query methods.
Always use prisma migrate deploy in production—never use prisma migrate dev, which is designed for development workflows. For production deployments, follow the sequence: test migrations in staging environments first, then run prisma migrate deploy to safely apply migrations without resetting data.
Preparing for Production
Configure your package.json scripts for production builds:
{
"scripts": {
"build": "npm run prisma:generate && strapi build",
"start": "NODE_ENV=production node server.js",
"prisma:generate": "prisma generate",
"prisma:migrate:deploy": "prisma migrate deploy",
"deploy": "npm run prisma:migrate:deploy && npm run build && npm run start"
}
}Set up your production environment variables with proper connection pooling. Configure connection limits and pool settings in your database URL or Knex.js pool configuration (e.g., pool: { min: 2, max: 10 } for typical applications):
NODE_ENV=production
APP_KEYS=your-app-keys-here
API_TOKEN_SALT=your-api-token-salt
ADMIN_JWT_SECRET=your-admin-jwt-secret
JWT_SECRET=your-jwt-secret
DATABASE_CLIENT=postgres
DATABASE_HOST=your-db-host
DATABASE_PORT=5432
DATABASE_NAME=strapi_production
DATABASE_USERNAME=strapi_user
DATABASE_PASSWORD=your-secure-password
DATABASE_SSL=true
DATABASE_URL="postgresql://prisma_user:password@your-db-host:5432/prisma_production?schema=prisma&connection_limit=10&pool_timeout=20"For production environments using connection pooling, configure the Prisma DATABASE_URL with specific pooling parameters:
DATABASE_URL="postgresql://prisma_user:password@your-db-host:5432/prisma_production?schema=prisma&connection_limit=10&pool_timeout=20"This configuration specifies:
- connection_limit=10: Maximum number of connections in the pool (exact limit set by this parameter; recommended values depend on your CPU count, deployment model, and workload rather than a fixed 10–20 range)
- pool_timeout=20: Connection acquisition timeout in seconds (prevents indefinite waiting when pool is exhausted)
- schema=prisma: Isolates Prisma tables in a separate PostgreSQL schema, maintaining separation from Strapi's tables
Typical applications benefit from connection pool sizes between 10-20 connections, though the optimal number depends on application concurrency patterns and database load.
Project Example: Hybrid E-commerce Platform
This example demonstrates a hybrid e-commerce platform using a microservices architecture where Strapi (operating as a separate service) manages product catalogs and marketing content through its built-in ORM, while Prisma (in an independent backend service) handles order processing and inventory management.
The separation requires maintaining distinct database schemas or instances for each service, allowing you to leverage Strapi's admin panel for content editors while maintaining type-safe transactional operations through Prisma in your separate application service. Note that Strapi and Prisma do not integrate at the ORM level; instead, they operate as completely independent services that your frontend consumes through separate APIs.
The code examples in this section demonstrate architectural patterns and API usage. Field names, types, and specific implementation details are illustrative and should be adapted to your business requirements.
Architecture Overview
The platform consists of three layers:
- Frontend: Next.js application consuming APIs from independent services.
- Content Layer (Strapi): Product information, categories, blog posts, media assets—managed through Strapi's Knex.js ORM.
- Transaction Layer (Prisma): Separate service managing shopping carts, orders, payment records, inventory tracking through Prisma ORM with independent database schema.
- Architecture Pattern: Microservices approach where Strapi and Prisma operate as completely independent services, each with its own database schema or instance, communicating through separate API endpoints.
Strapi Content Types
Create a Product collection type in Strapi's admin panel using the Content-Type Builder with fields for name, description, price, and images. Strapi manages the content through its Document Service API, which is built on top of the Query Engine API, providing your marketing team with a user-friendly interface to update product information while the underlying database layer handles data persistence and schema management.
The Strapi API endpoint provides product data:
GET /api/products?populate=*Prisma Transaction Models
Define transaction-specific models in your schema.prisma:
// Example schema for demonstration purposes. Adapt field types and relations to your specific business requirements.
model Order {
id String @id @default(uuid())
userId String
status OrderStatus @default(PENDING)
total Decimal @db.Decimal(10, 2)
items OrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([status])
}
model OrderItem {
id String @id @default(uuid())
orderId String
productId String // References Strapi product
quantity Int
priceAtTime Decimal @db.Decimal(10, 2)
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
@@index([orderId])
@@index([productId])
}
model InventoryRecord {
id String @id @default(uuid())
productId String @unique // References Strapi product
quantity Int
reserved Int @default(0)
lastRestocked DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([productId])
}
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}Run the migration to create these tables:
npx prisma migrate dev --name add_order_modelsOrder Processing Service
The following service demonstrates a SEPARATE Prisma-based backend service that is architecturally independent from Strapi. This service communicates with Strapi through API calls to fetch product information but manages all order data in its own Prisma-managed database schema. Save as order-service/src/services/orderService.ts (independent backend):
// ORDER SERVICE (Separate Prisma-based microservice - NOT part of Strapi)
// This runs as a completely independent backend from Strapi on a separate port
import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();
export const orderService = {
async createOrder(userId: string, items: Array<{ productId: string; quantity: number }>) {
// Step 1: Fetch product data from Strapi CMS service via API call
// This demonstrates the microservices pattern - we call Strapi's API, not its ORM
const productIds = items.map(item => item.productId);
const productsResponse = await fetch('http://strapi-service:1337/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STRAPI_API_TOKEN}`
},
body: JSON.stringify({ ids: productIds })
});
const { data: products } = await productsResponse.json();
// Step 2: Calculate total and prepare order items
let total = 0;
const orderItems = items.map(item => {
const product = products.find(p => p.id === item.productId);
if (!product) throw new Error(`Product ${item.productId} not found`);
const itemTotal = product.attributes.price * item.quantity;
total += itemTotal;
return {
productId: item.productId,
quantity: item.quantity,
priceAtTime: product.attributes.price
};
});
// Step 3: Create order in Prisma database (separate from Strapi's database)
// This uses ONLY Prisma Client - no Strapi ORM integration
const order = await prisma.order.create({
data: {
userId,
total,
status: 'PENDING',
items: {
create: orderItems
}
},
include: {
items: true
}
});
// Step 4: Update inventory through Prisma (in same service's database)
for (const item of items) {
await prisma.inventoryRecord.update({
where: { productId: item.productId },
data: {
reserved: {
increment: item.quantity
}
}
});
}
return order;
},
async getOrdersByUser(userId: string) {
return await prisma.order.findMany({
where: { userId },
include: { items: true },
orderBy: { createdAt: 'desc' }
});
},
async updateOrderStatus(orderId: string, status: Prisma.OrderStatus) {
return await prisma.order.update({
where: { id: orderId },
data: { status }
});
}
};
// ARCHITECTURAL NOTE:
// This service runs as a completely independent backend from Strapi:
// - Strapi service (default port 1337): Manages products through Strapi's standard content-type system and Entity/Document Service layer, which is backed by Knex.js in SQL databases
// - Order service (separate port): Manages orders via Prisma in separate database
// - Frontend: Calls both services through separate API endpoints
// - No shared ORM or database schema - complete separationFrontend Integration
Your Next.js frontend consumes both APIs as independent services. Fetch product information from Strapi through its separate API endpoint for display, and use the Prisma-powered API for order operations.
Note that Strapi and Prisma operate as completely independent services with separate database schemas—they are not integrated at the ORM level, but rather function as distinct microservices that your frontend coordinates through separate API calls.
// EXAMPLE PATTERN: This code illustrates the microservices communication pattern.
// Actual implementation details will vary based on your architecture.
// Fetch products from Strapi service (separate microservice on port 1337)
// Note: This demonstrates fetching from a standalone Strapi CMS instance
async function getProducts() {
const response = await fetch('http://strapi-service:1337/api/products?populate=*', {
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_STRAPI_API_TOKEN}`
}
});
const data = await response.json();
return data.data;
}
// Create order through separate Prisma-based order service (on port 3001)
// Note: These are two completely independent services:
// - Service A (port 1337): Strapi for content management
// - Service B (port 3001): Prisma backend for transactional data
async function createOrder(
userId: string,
items: Array<{ productId: string; quantity: number }>
): Promise<{ id: string; userId: string; items: Array<any>; status: string }> {
try {
// Call to independent Prisma-based order service
// NOT integrated with Strapi - separate microservice with its own database schema
const response = await fetch('http://order-service:3001/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_ORDER_API_TOKEN}`
},
body: JSON.stringify({
userId,
items
})
});
if (!response.ok) {
throw new Error(`Order service error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to create order:', error);
throw error;
}
}
// Fetch user orders from separate Prisma order service (NOT from Strapi)
async function getUserOrders(userId: string) {
const response = await fetch(`http://order-service:3001/api/orders/user/${userId}`, {
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_ORDER_API_TOKEN}`
}
});
return await response.json();
}Inventory Synchronization
Create a service that syncs inventory levels with Strapi's product availability using a microservices architecture, where both systems operate independently with separate database schemas:
// EXAMPLE PATTERN: This code illustrates the microservices communication pattern.
// Actual implementation details will vary based on your architecture.
// CORRECT APPROACH: Communication between independent services via APIs
export default {
async syncInventoryToStrapi() {
try {
// Fetch inventory records from your Prisma service (separate backend on port 3001)
const inventoryResponse = await fetch('http://order-service:3001/api/inventory', {
headers: {
'Authorization': `Bearer ${process.env.INTERNAL_API_TOKEN}`
}
});
const inventoryRecords = await inventoryResponse.json();
// Update Strapi products via its REST API (separate service on port 1337)
// This uses Strapi's Document Service internally, but we call it via API
for (const record of inventoryRecords) {
const availableQuantity = record.quantity - record.reserved;
await fetch(`http://strapi-service:1337/api/products/${record.productId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.STRAPI_API_TOKEN}`
},
body: JSON.stringify({
data: {
inStock: availableQuantity > 0,
stockLevel: availableQuantity
}
})
});
}
} catch (error) {
console.error('Inventory sync failed:', error);
throw error;
}
}
};
// ARCHITECTURAL NOTE:
// Strapi's database documentation and GitHub Issue #10129 confirm that
// Prisma and Strapi require a microservices architecture:
// - Service A (Strapi - port 1337): Uses Knex.js ORM, manages products
// - Service B (Prisma - port 3001): Separate backend manages inventory in separate database
// - Communication: REST APIs between services, not shared ORM access
//
// Do NOT attempt to mix Prisma and Strapi queries on the same database,
// as this causes migration conflicts, schema drift, and compatibility issues.This pattern requires running Strapi and Prisma as completely independent services with separate database schemas. Content editors manage product information through Strapi's admin panel and REST API, while developers maintain a separate Prisma backend service for transactional operations like order processing.
The frontend consumes both APIs separately—fast product catalog access through Strapi's REST API and order processing through the Prisma service with its transactional guarantees.
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST.
For more details, visit the Strapi documentation and Prisma documentation.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.