Skip to content

Commit

Permalink
Added Remix Accelerate Example App (#6151)
Browse files Browse the repository at this point in the history
* (feat) Added Remix Accelerate Example App

* Added seed command

* Updated Readme and added GIF

* Updated Readme file and added screenshot

* Removed log

* Resolved whitespaces

* Removed as `any`

* Updated the .env.example

* Added new GIF
  • Loading branch information
nurul3101 authored Jul 1, 2024
1 parent d2793ba commit 37558d1
Show file tree
Hide file tree
Showing 44 changed files with 1,367 additions and 0 deletions.
3 changes: 3 additions & 0 deletions accelerate/remix-starter/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DIRECT_DATABASE_URL="__YOUR_DATABASE_CONNECTION_STRING__"
DATABASE_URL="__YOUR_ACCELERATE_CONNECTION_STRING__"
NODE_ENV="development"
7 changes: 7 additions & 0 deletions accelerate/remix-starter/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.cache
build
public/build
app/styles
dist/
node_modules
api/
68 changes: 68 additions & 0 deletions accelerate/remix-starter/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"parser": "@typescript-eslint/parser",
"globals": {
"module": true,
"require": true,
"process": true,
"exports": true
},
"settings": {
"react": {
"version": "detect"
},
"formComponents": ["Form"],
"linkComponents": [
{ "name": "Link", "linkAttribute": "to" },
{ "name": "NavLink", "linkAttribute": "to" }
]
},
"plugins": [
"prettier",
"@typescript-eslint",
"jsx-a11y",
"import",
"react",
"react-hooks",
"lodash"
],
"extends": [
"eslint:recommended",
"prettier",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jsx-a11y/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:react/jsx-runtime",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"@remix-run/eslint-config/node"
],
"rules": {
"lodash/import-scope": [2, "method"],
"prettier/prettier": "error",
"import/no-unresolved": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
],
"jsx-a11y/anchor-is-valid": [
"error",
{
"components": ["Link", "NavLink"],
"specialLink": ["to"]
}
],
"react/boolean-prop-naming": "error",
"react/react-in-jsx-scope": "off",
"react/button-has-type": "error",
"react/jsx-no-target-blank": [
"error",
{
"warnOnSpreadAttributes": true,
"links": true,
"forms": true
}
]
}
}
12 changes: 12 additions & 0 deletions accelerate/remix-starter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
.cache
.output
build
public/build
api/*
.env
test-results/
playwright-report/
.eslintcache

!.env.example
2 changes: 2 additions & 0 deletions accelerate/remix-starter/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
auto-install-peers=false
shamefully-hoist=true
3 changes: 3 additions & 0 deletions accelerate/remix-starter/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"trailingComma": "all"
}
1 change: 1 addition & 0 deletions accelerate/remix-starter/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 20.14.0
Binary file added accelerate/remix-starter/Prisma-Studio-Image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions accelerate/remix-starter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Prisma Accelerate Example: Remix Starter

This project showcases how to use Prisma ORM with Prisma Accelerate in a Remix application. It [demonstrates](./app/routes/_index.tsx#L10-13) every available [caching strategy in Accelerate](https://www.prisma.io/docs/data-platform/accelerate/concepts#cache-strategies).

## Prerequisites

To successfully run the project, you will need the following:

- The **connection string** of a publicly accessible database.
- 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)).

## Getting started

### 1. Clone the repository

Clone the repository, navigate into it and install dependencies:

```
git clone [email protected]:prisma/prisma-examples.git --depth=1
cd prisma-examples/accelerate/remix-starter
npm install
```

### 2. Configure environment variables

Copy the `.env.example` env file in the root of the project directory:

```bash
cp .env.example .env
```

Now, open the `.env` file and set the `DIRECT_DATABASE_URL` and `DATABASE_URL` environment variables with the values of your connection string and your Accelerate connection string respectively:

```bash
# .env

# Accelerate connection string (used for queries by Prisma Client)
DATABASE_URL="__YOUR_ACCELERATE_CONNECTION_STRING__"

# Database connection string (used for migrations by Prisma Migrate)
DIRECT_DATABASE_URL="__YOUR_DATABASE_CONNECTION_STRING__"

```

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__`.

### 3. Run a migration to create the `Quotes` table and seed the database

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:

```
npx prisma migrate dev --name init
```

You now have an empty `Quotes` table in your database. Next, run the [seed script](./prisma/seed.ts) to create some sample records in the table:

```
npx prisma db seed
```

### 4. Generate Prisma Client for Accelerate

When using Accelerate, Prisma Client doesn't need a query engine. That's why you should generate it as follows:

```
npx prisma generate --no-engine
```

### 5. Start the app

You can run the app with the following command:

```
npm run dev
```

The application will start on PORT 5173 and you should be able to see the performance and other stats (e.g. cache/hit) for the different Accelerate cache strategies at the bottom of the UI:

![Demo](./Remix-accelerate.gif)

This application queries the most recent Quote with all the different cache strategies available in Accelerate.

Optionally, to add your own quote and see the caching strategies in action, you can add a new quote through Prisma Studio by running the following command:

```
npx prisma studio
```

Once the Prisma Studio is running, you can add a new quote by clicking on the `Quotes` table and then the `Add Record` button as shown in the screenshot below.

![Prisma Studio](./Prisma-Studio-Image.png)

After adding a new record, you can refresh the Remix application to see the new quote and the caching strategies in action.

## Resources

- [Accelerate Speed Test](https://accelerate-speed-test.vercel.app/)
- [Accelerate documentation](https://www.prisma.io/docs/accelerate)
- [Prisma Discord](https://pris.ly/discord)
Binary file added accelerate/remix-starter/Remix-accelerate.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions accelerate/remix-starter/app/components/Quote/Quote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable react/prop-types */
import { QuoteWrapper } from "./QuoteWrapper";
import { QuoteCacheType, QuoteResult } from "./../../lib/types";
import pkg from "openflights-cached";
const { findIATA } = pkg;

export const Quote: React.FC<{
title: string;
type: QuoteCacheType;
result: QuoteResult;
}> = ({ title, type, result }) => {
const [{ id, quote, createdAt }, { cacheStatus, region, lastModified }] = [
result.data,
result.info,
];

return (
<QuoteWrapper title={title} type={type}>
<div className="flex flex-col">
<p className="text-lg">
<span className="text-green-300">ID {id} </span>{'"'}
{quote}
{'"'}
</p>
<br />
<p className="text-lg">
<span className="font-bold mt-3.5">Created At</span>
{new Date(createdAt).toLocaleString("en-US")}
</p>

<div className="my-2 h-0.5 border-t-0 bg-neutral-100 opacity-100 dark:opacity-50"></div>

<div>
<br />
<p>
Cache Node Region ⸺
<span className="font-bold" style={{ marginLeft: "4px" }}>
{findIATA(region)?.city ?? region}
</span>
</p>
<br />
<p>
Cached Modified at ⸺
<span className="font-bold" style={{ marginLeft: "4px" }}>
{new Date(lastModified).toLocaleString("en-US")}
</span>
</p>
<br />
<p>
Cache status ⸺
<span
className={`font-bold ${
cacheStatus === "swr" || cacheStatus === "ttl"
? "text-green-400"
: "text-red-400"
}`}
style={{ marginLeft: "4px" }}
>
{cacheStatus
.toUpperCase()
.concat(
cacheStatus === "swr" || cacheStatus === "ttl"
? " CACHE HIT"
: "",
)}
</span>
</p>
<br />
<p>
Time taken: <span className="font-bold"> {result.time}ms</span>
</p>
<br />
</div>
</div>
</QuoteWrapper>
);
};
24 changes: 24 additions & 0 deletions accelerate/remix-starter/app/components/Quote/QuoteWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable react/prop-types */
import { QuoteCacheType } from "../../lib/types";
import { ReactNode } from "react";

export const QuoteWrapper: React.FC<{
title: string;
type: QuoteCacheType;
children: ReactNode;
}> = ({ title, type, children }) => {
return (
<div className="w-full h-full bg-white border border-gray-200 rounded-lg shadow p-4 dark:bg-gray-800 dark:border-gray-700">
<div className="flex items-center justify-between">
<h5 className="text-xl font-bold leading-none text-gray-900 dark:text-white">
{title}
</h5>
<div className="w-16"></div>
<p className="text-xl font-medium text-blue-600 dark:text-blue-500">
{type}
</p>
</div>
<div>{children}</div>
</div>
);
};
21 changes: 21 additions & 0 deletions accelerate/remix-starter/app/components/Quote/Quotes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// app/components/Quotes.tsx
import { Quote } from "./Quote";
import { QuoteResult } from "~/lib/types";

interface QuotesProps {
ttl: QuoteResult;
swr: QuoteResult;
both: QuoteResult;
none: QuoteResult;
}

export default function Quotes({ ttl, swr, both, none }: QuotesProps) {
return (
<div className="w-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<Quote title="Cached Quote" type="TTL" result={ttl}></Quote>
<Quote title="Cached Quote" type="SWR" result={swr}></Quote>
<Quote title="Cached Quote" type="TTL + SWR" result={both}></Quote>
<Quote title="Quote" type="No caching" result={none}></Quote>
</div>
);
}
17 changes: 17 additions & 0 deletions accelerate/remix-starter/app/components/Quote/RefreshQuote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Toaster } from "react-hot-toast";

export const RefreshQuote = () => {
return (
<>
<Toaster position="top-center" reverseOrder={false} />

<button
type="button"
onClick={() => window.location.reload()}
className="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 dark:focus:ring-green-800"
>
Refresh
</button>
</>
);
};
23 changes: 23 additions & 0 deletions accelerate/remix-starter/app/data/query.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import prisma from "./utils/prisma.server";
import { CacheStrategy } from "../lib/types";

export const getQuotes = async (strategy?: CacheStrategy) => {
const start = Date.now();

const result = await prisma.quotes
.findMany({
// 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.
cacheStrategy: strategy,
orderBy: {
id: "desc",
},
take: 1,
})
.withAccelerateInfo();

return {
data: result?.data?.[0],
info: result.info,
time: Date.now() - start,
};
};
Loading

0 comments on commit 37558d1

Please sign in to comment.