Skip to content

Commit 67063e4

Browse files
ludralphankur-arch
andauthored
feat:add nuxt example (#6164)
* feat:add nuxt example * fix: adjust css * chore: update readme * chore:remove comment * feat: add refresh button * chore: update readme and config * feat: add loader * chore:update readme * Update accelerate/nuxtjs-starter/README.md * chore: add gif * chore: update demo file --------- Co-authored-by: Ankur Datta <[email protected]>
1 parent 08ceae7 commit 67063e4

File tree

19 files changed

+540
-0
lines changed

19 files changed

+540
-0
lines changed

accelerate/nuxtjs-starter/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Nuxt dev/build outputs
2+
.output
3+
.data
4+
.nuxt
5+
.nitro
6+
.cache
7+
dist
8+
9+
# Node dependencies
10+
node_modules
11+
12+
# Logs
13+
logs
14+
*.log
15+
16+
# Misc
17+
.DS_Store
18+
.fleet
19+
.idea
20+
21+
# Local env files
22+
.env
23+
.env.*
24+
!.env.example

accelerate/nuxtjs-starter/README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Prisma Accelerate Example: Nuxt.js Starter
2+
3+
## Introduction
4+
The quote generator retrieves the most recently added quote with and without caching enabled from a predefined set of quotes. In the context of your Nuxtjs application, the quote generator is a service that provides quotes, with some caching strategies applied to optimize performance and reduce the load on the database.
5+
6+
## General Steps of the Quote Generator:
7+
- Fetch a quote: Retrieves the most recently added quote with and without caching enabled.
8+
- Apply caching strategy: Depending on the request, it may apply different caching strategies (e.g., TTL, SWR, TTL+SWR or no caching).
9+
- Return the quote: The selected quote is returned, along with metadata about the caching status and other relevant information.
10+
11+
This project showcases how to use Prisma ORM with Prisma Accelerate in a Nuxt.js application. It [demonstrates](./server/api/quotes.ts#L11-27) every available [caching strategy in Accelerate](https://www.prisma.io/docs/accelerate/caching#cache-strategies).
12+
13+
## Prerequisites
14+
15+
To successfully run the project, you will need the following:
16+
17+
- The **connection string** of a publicly accessible database
18+
- Your **Accelerate connection string** (containing your **Accelerate API key**) which you can get by enabling Accelerate in a project in your [Prisma Data Platform](https://pris.ly/pdp) account (learn more in the [docs](https://www.prisma.io/docs/platform/concepts/environments#api-keys))
19+
20+
## Getting started
21+
22+
### 1. Clone the repository
23+
24+
Clone the repository, navigate into it and install dependencies:
25+
26+
```
27+
git clone [email protected]:prisma/prisma-examples.git --depth=1
28+
cd prisma-examples/accelerate/nuxtjs-starter
29+
npm install
30+
```
31+
32+
### 2. Configure environment variables
33+
34+
Create a `.env` in the root of the project directory:
35+
36+
```bash
37+
touch .env
38+
```
39+
40+
Now, open the `.env` file and set the `DATABASE_URL` and `DIRECT_URL` environment variables with the values of your Postgres connection string and your Accelerate connection string:
41+
42+
```bash
43+
# .env
44+
45+
# Accelerate connection string (used for queries by Prisma Client)
46+
DATABASE_URL="__YOUR_ACCELERATE_CONNECTION_STRING__"
47+
48+
# Database connection string (used for migrations by Prisma Migrate)
49+
DIRECT_URL="__YOUR_DATABASE_CONNECTION_STRING__"
50+
51+
NUXT_PUBLIC_URL="http://localhost:3000"
52+
```
53+
54+
Note that `__YOUR_DATABASE_CONNECTION_STRING__` and `__YOUR_ACCELERATE_CONNECTION_STRING__` are placeholder values that you need to replace with the values of your database and Accelerate connection strings. Notice that the Accelerate connection string has the following structure: `prisma://accelerate.prisma-data.net/?api_key=__YOUR_ACCELERATE_API_KEY__`.
55+
56+
### 3. Run a migration to create the `Quotes` table and seed the database
57+
58+
The Prisma schema file contains a single `Quotes` model. You can map this model to the database and create the corresponding `Quotes` table using the following command:
59+
60+
```
61+
npx prisma migrate dev --name init
62+
```
63+
64+
You now have an empty `Quotes` table in your database. Next, run the [seed script](./prisma/seed.js) to create some sample records in the table:
65+
66+
```
67+
npx prisma db seed
68+
```
69+
70+
### 4. Generate Prisma Client for Accelerate
71+
72+
When using Accelerate, Prisma Client doesn't need a query engine. That's why you should generate it as follows:
73+
74+
```
75+
npx prisma generate --no-engine
76+
```
77+
78+
### 5. Start the app
79+
80+
You can run the app with the following command:
81+
82+
```
83+
npm run dev
84+
```
85+
86+
Starting the development server will skip the prompt to install the Prisma CLI and create a
87+
Prisma Schema as they already exist. It will also skip the prompt to migrate the schema changes to the database, as the migrations folder already exists from step 3. You would then need to agree to the prompt to install and access Prisma Studio from the Nuxt Devtools:
88+
89+
```
90+
Do you want to view and edit your data by installing Prisma Studio in Nuxt DevTools? › (Y/n)
91+
Select Y (yes) to install and use Prisma Studio in Nuxt Devtools.
92+
```
93+
94+
### 6. Launch the app in the browser
95+
Open the browser and navigate to http://localhost:3000
96+
97+
![Screenshot](./demo.gif)
98+
99+
You can see the performance and other stats (e.g. cache/hit) for the different Accelerate cache strategies at the bottom of the UI:
100+
101+
## Resources
102+
103+
- [Accelerate Speed Test](https://accelerate-speed-test.vercel.app/)
104+
- [Accelerate documentation](https://www.prisma.io/docs/accelerate)
105+
- [Prisma Discord](https://pris.ly/discord)

accelerate/nuxtjs-starter/app.vue

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<template>
2+
<div id="app">
3+
<main class="flex min-h-screen flex-col items-center justify-start p-8 md:p-24">
4+
<h1 class="text-3xl text-slate-800 text-black">
5+
Accelerated Quotes
6+
</h1>
7+
<br />
8+
<p class="text-xl text-slate-800">
9+
Retrieves the most recently added quote with and without caching enabled
10+
</p>
11+
<br />
12+
<button @click="refreshPage" class="focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-green-600 dark:hover:bg-green-700 focus:ring-green-800">
13+
Refresh
14+
</button>
15+
<NuxtPage />
16+
</main>
17+
</div>
18+
</template>
19+
20+
<script setup lang="ts">
21+
const refreshPage = () => {
22+
window.location.reload();
23+
};
24+
</script>
25+
26+
<style>
27+
/* Add global styles here */
28+
html,
29+
body {
30+
margin: 0;
31+
padding: 0;
32+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
33+
background-color: #f0f0f0;
34+
}
35+
</style>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<QuoteWrapper :title="title" :type="type">
3+
<div class="flex flex-col">
4+
<div v-if="loading" class="text-lg text-gray-500">Loading...</div>
5+
<p v-else-if="result?.data" class="text-lg">
6+
<span class="text-green-300">ID {{ id }}</span> ⸺ '"
7+
<span v-html="quote"></span>
8+
"'
9+
</p>
10+
<br v-if="result?.data" />
11+
<p v-if="result?.data" class="text-lg font-normal text-black ">
12+
<span class="mt-3.5">Created At</span> ⸺
13+
{{ formatDate(createdAt) }}
14+
</p>
15+
<div v-if="result?.data" class="my-2 h-0.5 border-t-0 bg-neutral-100 opacity-100 opacity-50"></div>
16+
<div v-if="result?.data">
17+
<br />
18+
<p>
19+
Cache Node Region ⸺
20+
<span class="font-bold">
21+
{{ findIATA(region)?.city || region }}
22+
</span>
23+
</p>
24+
<br />
25+
<p>
26+
Cached Modified at ⸺
27+
<span class="font-bold">
28+
{{ formatDate(lastModified) }}
29+
</span>
30+
</p>
31+
<br />
32+
<p>
33+
Cache status ⸺
34+
<span class="font-bold" :class="{
35+
'text-green-400': cacheStatus === 'swr' || cacheStatus === 'ttl',
36+
'text-red-400': cacheStatus !== 'swr' && cacheStatus !== 'ttl'
37+
}">
38+
{{ cacheStatus?.toUpperCase() }}
39+
{{ cacheStatus === 'swr' || cacheStatus === 'ttl'
40+
? ' CACHE HIT'
41+
: '' }}
42+
</span>
43+
</p>
44+
<br />
45+
<p>
46+
Time taken: <span class="font-bold"> {{ result.time }}ms</span>
47+
</p>
48+
<br />
49+
</div>
50+
<p v-else class="text-red-500">No data available</p>
51+
</div>
52+
</QuoteWrapper>
53+
</template>
54+
55+
<script setup lang="ts">
56+
import { defineProps, computed, ref, onMounted } from 'vue';
57+
import QuoteWrapper from './QuoteWrapper.vue';
58+
import type { QuoteResult, QuoteCacheType } from '@/lib/types';
59+
import { findIATA } from 'openflights-cached';
60+
61+
const props = defineProps<{
62+
title: string;
63+
type: QuoteCacheType;
64+
result: QuoteResult | null;
65+
}>();
66+
67+
const loading = ref(true);
68+
69+
const id = computed(() => props.result?.data?.id);
70+
const quote = computed(() => props.result?.data?.quote);
71+
const createdAt = computed(() => props.result?.data?.createdAt || '');
72+
const region = computed(() => props.result?.info?.region || '');
73+
const lastModified = computed(() => props.result?.info?.lastModified || '');
74+
const cacheStatus = computed(() => props.result?.info?.cacheStatus || '');
75+
76+
function formatDate(date: string | undefined) {
77+
return date ? new Date(date).toLocaleString('en-US') : 'N/A';
78+
}
79+
80+
onMounted(() => {
81+
if (props.result) {
82+
loading.value = false;
83+
}
84+
});
85+
</script>
86+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<template>
2+
<div
3+
class="w-full h-full bg-white border border-gray-200 rounded-lg shadow p-4 bg-gray-800 border-gray-700">
4+
<div class="flex items-center justify-between">
5+
<h5 class="text-xl font-bold leading-none text-gray-900 text-white">
6+
{{ title }}
7+
</h5>
8+
<div class="w-16"></div>
9+
<p class="text-xl font-medium text-blue-600 text-blue-500">
10+
{{ type }}
11+
</p>
12+
</div>
13+
<div>
14+
<slot></slot>
15+
</div>
16+
</div>
17+
</template>
18+
19+
<script setup lang="ts">
20+
import { defineProps } from 'vue';
21+
import type { QuoteCacheType } from '@/lib/types';
22+
23+
const props = defineProps<{
24+
title: string;
25+
type: QuoteCacheType;
26+
}>();
27+
</script>

accelerate/nuxtjs-starter/demo.gif

17.4 MB
Loading
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { PrismaClient } from '@prisma/client'
2+
import { withAccelerate } from '@prisma/extension-accelerate'
3+
4+
const prismaClientSingleton = () => {
5+
return new PrismaClient().$extends(withAccelerate())
6+
}
7+
8+
declare const globalThis: {
9+
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
10+
} & typeof global;
11+
12+
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
13+
14+
export default prisma
15+
16+
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export type CacheStrategy =
2+
| {
3+
ttl: number;
4+
swr: number;
5+
}
6+
| {
7+
ttl: number;
8+
}
9+
| {
10+
swr: number;
11+
};
12+
13+
export type AccelerateInfo = {
14+
cacheStatus: "ttl" | "swr" | "miss" | "none";
15+
lastModified: Date;
16+
region: string;
17+
requestId: string;
18+
signature: string;
19+
};
20+
21+
export type Quote = {
22+
id: number;
23+
quote: string;
24+
createdAt: string;
25+
};
26+
27+
export type QuoteResult = {
28+
data: Quote;
29+
info: AccelerateInfo;
30+
time: number;
31+
};
32+
33+
export type QuoteCacheType = "SWR" | "TTL" | "No caching" | "TTL + SWR";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export async function delay(ms: number): Promise<void> {
2+
return new Promise<void>((resolve) => {
3+
setTimeout(() => {
4+
resolve();
5+
}, ms);
6+
});
7+
}
8+
9+
export const HOST = process.env.NUXT_PUBLIC_URL ?? "http://localhost:3000";
10+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import prisma from "../prisma";
2+
import type { CacheStrategy } from "../types";
3+
4+
export const getQuotes = async (strategy?: CacheStrategy) => {
5+
const start = Date.now();
6+
7+
const result = await prisma.quotes
8+
.findMany({
9+
// You can find the `cacheStrategy` options [here](https://www.prisma.io/docs/accelerate/caching#cache-strategies). The `cacheStrategy` can also be undefined, which would mean only connection pooling is being used.
10+
cacheStrategy: strategy,
11+
orderBy: {
12+
id: "desc",
13+
},
14+
take: 1,
15+
})
16+
.withAccelerateInfo();
17+
18+
19+
return {
20+
data: result?.data?.[0],
21+
info: result.info,
22+
time: Date.now() - start,
23+
};
24+
};
25+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// https://nuxt.com/docs/api/configuration/nuxt-config
2+
export default defineNuxtConfig({
3+
modules: ["@prisma/nuxt", "tailwindcss", "@nuxtjs/tailwindcss"],
4+
prisma:{
5+
autoSetupPrisma: false,
6+
},
7+
devtools: { enabled: true }
8+
})

0 commit comments

Comments
 (0)