This repo demonstrates usage of Prisma Postgres security rules which enables database access from the frontend.
Note: Security rules is a new feature of Prisma Postgres that's currently in Early Access and not yet suitable for production use. If you try it out, please share your feedback with us to help shape its API and overall DX.
The app is based on Next.js 15 with App Router, NextAuth.js. It purposely uses "use client"
in its component to demonstrate how the database can be securely accessed from the frontend.
Run the following commands to set up the repo on your local machine:
git clone [email protected]:prisma/nextjs-security-rules-demo.git
cd nextjs-security-rules-demo
npm install
Create a new Prisma Postgres database with this command:
npx prisma init --db
If you don't have a Prisma Console account yet, or if you are not logged in, the command will prompt you to log in using one of the available authentication providers. A browser window will open so you can log in or create an account. Return to the CLI after you have completed this step.
Once logged in (or if you were already logged in), the CLI will prompt you to:
- Select a region (e.g.
us-east-1
) - Enter a project name
After successful creation, you will see output similar to the following:
CLI output
Let's set up your Prisma Postgres database!
? Select your region: ap-northeast-1 - Asia Pacific (Tokyo)
? Enter a project name: testing-migration
✔ Success! Your Prisma Postgres database is ready ✅
We found an existing schema.prisma file in your current project directory.
--- Database URL ---
Connect Prisma ORM to your Prisma Postgres database with this URL:
prisma+postgres://accelerate.prisma-data.net/?api_key=...
--- Next steps ---
Go to https://pris.ly/ppg-init for detailed instructions.
1. Install and use the Prisma Accelerate extension
Prisma Postgres requires the Prisma Accelerate extension for querying. If you haven't already installed it, install it in your project:
npm install @prisma/extension-accelerate
...and add it to your Prisma Client instance:
import { withAccelerate } from "@prisma/extension-accelerate"
const prisma = new PrismaClient().$extends(withAccelerate())
2. Apply migrations
Run the following command to create and apply a migration:
npx prisma migrate dev
3. Manage your data
View and edit your data locally by running this command:
npx prisma studio
...or online in Console:
https://console.prisma.io/{workspaceId}/{projectId}/studio
4. Send queries from your app
If you already have an existing app with Prisma ORM, you can now run it and it will send queries against your newly created Prisma Postgres instance.
5. Learn more
For more info, visit the Prisma Postgres docs: https://pris.ly/ppg-docs
Locate and copy the database URL provided in the CLI output, you will need it in the next step.
Then, create a .env
file in the project root by renaming .env.example
:
mv .env.example .env
Now, paste the URL from the previous step into it as a value for the DATABASE_URL
environment variable. For example:
# .env
+DATABASE_URL=prisma+postgres://accelerate.prisma-data.net/?api_key=ey...
Next, add the following environment variables to .env
:
# .env
DATABASE_URL=prisma+postgres://accelerate.prisma-data.net/?api_key=ey...
+NEXTAUTH_URL="http://localhost:3000" # for running locally
+NEXTAUTH_SECRET="fgkv9eqocqWZyJL05FpquUFu5jlGXRZaQWHw4jBzOYM=" # generate your own string in production
Run the following command to create tables in your database. This creates the User
and Post
tables that are defined in prisma/schema.prisma
:
npx prisma migrate dev --name init
Now seed the database:
npm run seed
The prisma/rules.ts
file defines a set of security rules. For the rules to take effect on your Prisma Postgres instance, you need to deploy them with the following command:
npm run rules:deploy
Note: For convenience, the command is defined as an npm script in
package.json
. The full command looks as follows:prisma rules --early-access deploy my_rules -f ./prisma/rules.ts
.
This command outputs a public key. This key is used to instantite the authorized database client in lib/db.ts
:
export const authorizedClient = new AuthorizedClient<typeof rules>({
publicKey: process.env.NEXT_PUBLIC_SECURITY_RULES_PUBLIC_KEY,
});
The key is read as an environment variable, so go back to your .env
file and add it there:
# .env
DATABASE_URL=prisma+postgres://accelerate.prisma-data.net/?api_key=ey...
NEXTAUTH_URL="http://localhost:3000" # for running locally
NEXTAUTH_SECRET="fgkv9eqocqWZyJL05FpquUFu5jlGXRZaQWHw4jBzOYM=" # generate your own string in production
+NEXT_PUBLIC_SECURITY_RULES_PUBLIC_KEY="ey..."
Run the development server:
npm run dev
Open http://localhost:3000 with your browser to see the result.
You can create new users or log in with an existing one from seed.ts
, e.g.:
- Email:
[email protected]
- Password:
password123
The authorization logic for this app is defined in prisma/rules.ts
:
const rules = defineRules({
prisma: new PrismaClient(),
rules: {
$allModels: false,
user: true,
post: {
read: true,
create({ context }) {
if (context?.userId) {
return true;
}
return false;
},
update({ context }) {
if (context) {
return context.userId === context.authorIdOfPostToChange;
}
return false;
},
delete({ context }) {
if (context) {
return context.userId === context.authorIdOfPostToChange;
}
return false;
}
},
},
contextSchema: z.object({
userId: z.string().optional(),
authorIdOfPostToChange: z.string().optional(),
}),
});
The userId
and authorIdOfPostToChange
fields on the context
object are set in our application code, e.g. in the publishPost
Server Action:
async function publishPost() {
"use server";
const session = await getServerSession(authOptions);
if (!session) {
return { error: "You need to be authenticated to publish this post." };
}
authorizedClient.$rules.setGlobalContext({
userId: session?.user.id || "",
authorIdOfPostToChange: post?.author?.id || "",
});
await authorizedClient.post.update({
where: { id: postId },
data: { published: true },
});
redirect("/");
}
Before the query is executed against the database, it runs through the rules engine where the permission logic for the update
operation on post
models is evaluated:
const rules = defineRules({
// ...
rules: {
post: {
// ...
update({ context }) {
if (context) {
return context.userId === context.authorIdOfPostToChange;
}
return false;
},
// ...
},
},
// ...
});
If you want to change the rules, you need to deploy them again for the changes to take effect:
npm run rules:deploy
Please join our Discord to share your questions, thoughts and other feedback with us.