├── .env.example ├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── docs ├── cfrp.png └── show.png ├── package.json ├── packages ├── client │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── img │ │ │ ├── apartment.jpg │ │ │ └── clerk-logo.svg │ │ └── index.html │ ├── src │ │ ├── API │ │ │ ├── apartments.ts │ │ │ ├── fetcher.ts │ │ │ ├── index.ts │ │ │ └── user.ts │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── assets │ │ │ ├── clerk-logo.svg │ │ │ └── fonts │ │ │ │ └── Quicksand-Regular.ttf │ │ ├── components │ │ │ ├── ApartmentCard.module.css │ │ │ ├── ApartmentCard.tsx │ │ │ ├── Apartments.tsx │ │ │ ├── Button.module.css │ │ │ ├── Button.tsx │ │ │ ├── Footer.module.css │ │ │ ├── Footer.tsx │ │ │ ├── Header.module.css │ │ │ ├── Header.tsx │ │ │ ├── MyApartments.module.css │ │ │ └── MyApartments.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useApartments.ts │ │ │ └── useUserApartments.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── layouts │ │ │ ├── GridLayout.module.css │ │ │ └── GridLayout.tsx │ │ ├── react-app-env.d.ts │ │ ├── types │ │ │ └── index.ts │ │ └── utils │ │ │ └── cookies.ts │ └── tsconfig.json ├── db │ ├── .env.example │ ├── README.md │ ├── package.json │ ├── schema.prisma │ └── src │ │ ├── index.ts │ │ ├── models │ │ ├── Apartment.ts │ │ └── index.ts │ │ └── types.ts └── server │ ├── package.json │ ├── src │ ├── auth │ │ └── clerkHandler.ts │ ├── index.ts │ └── routes │ │ ├── apartments.ts │ │ └── user.ts │ └── tsconfig.json ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | # Port on which the server will receive connections 2 | SERVER_PORT= 3 | # Origin of the client app 4 | CLIENT_ORIGIN= 5 | # Clerk Secret for your application backend. You can retrieve it from https://dashboard.clerk.dev under Settings → API Keys 6 | CLERK_SECRET_KEY= 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.local 3 | node_modules -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["prisma.prisma"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clerk Fastify React Prisma Starter 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | 13 | This repo shows an example use case for how you setup a fullstack monorepo starter with [Clerk](https://clerk.dev?utm_source=github&utm_medium=starters&utm_campaign=cfrp), Fastify, React and Prisma to achieve authenticated cross-domain user access. 14 | 15 | # Clerk Apartments Application 16 | 17 | ## The application 18 | 19 | The **Clerk Apartments** application allows a user to claim apartments from the gallery and view them in his own collection. Any apartment that is "claimed" by a user, cannot be reclaimed unless "foregone" by the previous holder. 20 | 21 | ## Under the hood 22 | 23 | The example is a fullstack application in a monorepo structure using: 24 | 25 | - [Clerk](https://clerk.dev?utm_source=github&utm_medium=starters&utm_campaign=cfrp) as an authentication provider. 26 | - [Fastify](https://www.fastify.io/) as the API server. 27 | - [React](https://reactjs.org/) as the frontend library. 28 | - [Prisma](https://www.prisma.io/) for data storage and model type sharing between client and server. 29 | - [Yarn workspaces](https://yarnpkg.com/features/workspaces) for the monorepo management. 30 | 31 | ## Where the magic happens 32 | 33 | Authenticating Prisma data access using Clerk works by introducing a thin and customizable access management layer on top of the Prisma generated API for our collection. 34 | 35 | This ultimately gets handled in the Fastify API routes with simple logic and the use of the Clerk authentication [preHandler hook](./packages/server/src/auth/clerkHandler.ts), like in the [/apartments routes](./packages/server/src/routes/apartments.ts). 36 | 37 | ## Running the example 38 | 39 | To run the example locally you need to: 40 | 41 | 1. Sign up for a Clerk account at [https://clerk.dev/](http://clerk.dev/?utm_source=github&utm_medium=starters&utm_campaign=cfrp). 42 | 2. Clone this repository `git clone git@github.com:clerkinc/clerk-fastify-react-prisma-starter.git`. 43 | 3. Setup the required API variables from your Clerk project as shown at the example env files. [Server](./.env.example) [Client](./packages/client/.env.example) 44 | 4. `yarn install` to install the required dependencies. 45 | 5. Setup your Prisma database, following the [instructions](./packages/db/README.md) at the `db` folder. 46 | 6. To start both the client and the server you need to run in separate terminals from the top level of the repository the commands: `yarn client:dev` and `yarn server:dev` 47 | 48 | ## Contact 49 | 50 | If you have any specific use case or anything you would like to ask, please reach out! 51 | -------------------------------------------------------------------------------- /docs/cfrp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clerk/clerk-fastify-react-prisma-starter/678a6101221edb743c3b29218b41d59f14f885ce/docs/cfrp.png -------------------------------------------------------------------------------- /docs/show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clerk/clerk-fastify-react-prisma-starter/678a6101221edb743c3b29218b41d59f14f885ce/docs/show.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "clerk-fastify-react-prisma-starter", 4 | "workspaces": [ 5 | "packages/**/*" 6 | ], 7 | "scripts": { 8 | "server:dev": "nodemon --watch './packages/server/**/*.ts' --exec 'ts-node' ./packages/server/src/index.ts", 9 | "client:dev": "yarn workspace @cfrp/client start", 10 | "prisma:schema": "yarn workspace @cfrp/db generate", 11 | "prisma:studio": "yarn workspace @cfrp/db studio" 12 | }, 13 | "devDependencies": { 14 | "nodemon": "^2.0.14", 15 | "ts-node": "^10.4.0", 16 | "typescript": "^4.6.2" 17 | }, 18 | "engines": { 19 | "node": ">=14" 20 | }, 21 | "dependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /packages/client/.env.example: -------------------------------------------------------------------------------- 1 | # Clerk API endpoint for your application. You can retrieve it from https://dashboard.clerk.dev 2 | REACT_APP_CLERK_PUBLISHABLE_KEY= 3 | # The backend API host 4 | REACT_APP_API_HOST= -------------------------------------------------------------------------------- /packages/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/client/README.md: -------------------------------------------------------------------------------- 1 | # @cfrp/client 2 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cfrp/client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@clerk/clerk-react": "latest", 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^11.1.0", 9 | "@testing-library/user-event": "^12.1.10", 10 | "@types/jest": "^26.0.15", 11 | "@types/node": "^14.0.0", 12 | "@types/react": "^18.0.0", 13 | "@types/react-dom": "^18.0.0", 14 | "clsx": "^1.2.1", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.8.0", 18 | "react-scripts": "5.0.1", 19 | "typescript": "^4.6.2" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/client/public/img/apartment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clerk/clerk-fastify-react-prisma-starter/678a6101221edb743c3b29218b41d59f14f885ce/packages/client/public/img/apartment.jpg -------------------------------------------------------------------------------- /packages/client/public/img/clerk-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 21 | Clerk Apartments 22 | 23 | 24 | 25 |
26 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /packages/client/src/API/apartments.ts: -------------------------------------------------------------------------------- 1 | import { Apartment } from "../types"; 2 | import { fetcher } from "./fetcher"; 3 | 4 | export async function getApartments(): Promise { 5 | return await (await fetcher("/apartments")).json(); 6 | } 7 | 8 | export async function claimApartment(apartmentId: string): Promise { 9 | return await ( 10 | await fetcher( 11 | "/apartments/claim", 12 | { method: "POST", body: JSON.stringify({ apartmentId }) }, 13 | true 14 | ) 15 | ).json(); 16 | } 17 | -------------------------------------------------------------------------------- /packages/client/src/API/fetcher.ts: -------------------------------------------------------------------------------- 1 | import { getCookie } from "../utils/cookies"; 2 | 3 | export function fetcher( 4 | path: string, 5 | options: RequestInit = {}, 6 | auth: boolean = false 7 | ) { 8 | return fetch(`${process.env.REACT_APP_API_HOST}${path}`, { 9 | headers: { 10 | ...(auth && { Authorization: `Bearer ${getCookie("__session")}` }), 11 | }, 12 | ...options, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/client/src/API/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./apartments"; 2 | export * from "./user"; 3 | -------------------------------------------------------------------------------- /packages/client/src/API/user.ts: -------------------------------------------------------------------------------- 1 | import { Apartment } from "../types"; 2 | import { fetcher } from "./fetcher"; 3 | 4 | export async function getUserApartments(): Promise { 5 | return await (await fetcher("/user/apartments", {}, true)).json(); 6 | } 7 | 8 | export async function foregoApartment(apartmentId: string) { 9 | return await fetcher( 10 | "/user/forego", 11 | { method: "POST", body: JSON.stringify({ apartmentId }) }, 12 | true 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/client/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ClerkProvider } from "@clerk/clerk-react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { Apartments } from "./components/Apartments"; 4 | import { Header } from "./components/Header"; 5 | import { Routes, Route } from "react-router-dom"; 6 | import { MyApartments } from "./components/MyApartments"; 7 | import { Footer } from "./components/Footer"; 8 | 9 | // Get the Frontend API from the environment 10 | const publishableKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY || ""; 11 | 12 | function App() { 13 | const navigate = useNavigate(); 14 | return ( 15 | navigate(to)} 18 | > 19 |
20 | 21 | }> 22 | }> 23 | 24 |