diff --git a/docs/guides/manual-mode.md b/docs/guides/manual-mode.md index cb9cdd5bf98..78cc7b2f6d5 100644 --- a/docs/guides/manual-mode.md +++ b/docs/guides/manual-mode.md @@ -264,19 +264,19 @@ That includes things like database connections, caches, in-memory data structure Here's a utility that remembers any in-memory values you want to keep around across rebuilds: -```ts filename=app/utils/remember.ts -// adapted from https://github.com/jenseng/abuse-the-platform/blob/main/app/utils/singleton.ts -// thanks @jenseng! - -export function remember( - key: string, - getValue: () => T -) { +```ts filename=app/utils/singleton.server.ts +// Borrowed & modified from https://github.com/jenseng/abuse-the-platform/blob/main/app/utils/singleton.ts +// Thanks @jenseng! + +export const singleton = ( + name: string, + valueFactory: () => Value +): Value => { const g = global as any; - g.__remember ??= {}; - g.__remember[key] ??= getValue(); - return g.__remember[key]; -} + g.__singletons ??= {}; + g.__singletons[name] ??= valueFactory(); + return g.__singletons[name]; +}; ``` For example, to reuse a Prisma client across rebuilds: @@ -284,10 +284,13 @@ For example, to reuse a Prisma client across rebuilds: ```ts filename=app/db.server.ts import { PrismaClient } from "@prisma/client"; -import { remember } from "~/utils/remember"; +import { singleton } from "~/utils/singleton.server"; // hard-code a unique key so we can look up the client when this module gets re-imported -export const db = remember("db", () => new PrismaClient()); +export const db = singleton( + "prisma", + () => new PrismaClient() +); ``` [mental-model]: https://www.youtube.com/watch?v=zTrjaUt9hLo diff --git a/docs/tutorials/jokes.md b/docs/tutorials/jokes.md index de2151318da..13fe5cd749b 100644 --- a/docs/tutorials/jokes.md +++ b/docs/tutorials/jokes.md @@ -61,7 +61,7 @@ You can follow along with this tutorial on [CodeSandbox][code-sandbox] (a fantas - [npm][npm] 7 or greater - A code editor ([VSCode][vs-code] is a nice one) -If you'd like to follow along with the deploy step at the end, you'll also want an account on [Fly.io][fly-io]. +If you'd like to follow along with the deployment step at the end, you'll also want an account on [Fly.io][fly-io]. We'll also be executing commands in your system command line/terminal interface. So you'll want to be familiar with that. @@ -84,7 +84,7 @@ If you're planning on using CodeSandbox, you can use [the Basic example][the-bas 💿 Open your terminal and run this command: -```sh +```shellscript nonumber npx create-remix@latest ``` @@ -110,7 +110,7 @@ Once the `npm install` has completed, we'll change into the `remix-jokes` direct 💿 Run this command -```sh +```shellscript nonumber cd remix-jokes ``` @@ -147,12 +147,12 @@ Let's talk briefly about a few of these files: - `app/entry.server.tsx` - This is the first bit of your JavaScript that will run when a request hits your server. Remix handles loading all the necessary data, and you're responsible for sending back the response. We'll use this file to render our React app to a string/stream and send that as our response to the client. - `app/root.tsx` - This is where we put the root component for our application. You render the `` element here. - `app/routes/` - This is where all your "route modules" will go. Remix uses the files in this directory to create the URL routes for your app based on the name of the files. -- `public/` - This is where your static assets go (images/fonts/etc) +- `public/` - This is where your static assets go (images/fonts/etc.) - `remix.config.js` - Remix has a handful of configuration options you can set in this file. 💿 Let's go ahead and run the build: -```sh +```shellscript nonumber npm run build ``` @@ -167,7 +167,7 @@ Now you should also have a `.cache/` directory (something used internally by Rem 💿 Let's run the built app now: -```sh +```shellscript nonumber npm start ``` @@ -197,7 +197,7 @@ export default function App() { Remix: So great, it's funny! @@ -227,7 +227,7 @@ app 💿 With that set up, go ahead and start the dev server up with this command: -```sh +```shellscript nonumber npm run dev ``` @@ -285,7 +285,7 @@ export default function App() { Remix: So great, it's funny! @@ -506,7 +506,7 @@ export default function App() { Remix: So great, it's funny! @@ -1216,7 +1216,7 @@ export default function App() { Remix: So great, it's funny! @@ -1352,14 +1352,14 @@ There are two packages that we need to get started: 💿 Install the Prisma packages: -```sh +```shellscript nonumber npm install --save-dev prisma npm install @prisma/client ``` 💿 Now we can initialize Prisma with SQLite: -```sh +```shellscript nonumber npx prisma init --datasource-provider sqlite ``` @@ -1406,7 +1406,7 @@ model Joke { 💿 With that in place, run this: -```sh +```shellscript nonumber npx prisma db push ``` @@ -1439,7 +1439,7 @@ node_modules /prisma/dev.db ``` -If your database gets messed up, you can always delete the `prisma/dev.db` file and run `npx prisma db push` again. Remember to also restart your dev server with `npm run dev`. +If your database gets messed up, you can always delete the `prisma/dev.db` file and run `npx prisma db push` again. Next, we're going to write a little file that will "seed" our database with test data. Again, this isn't really remix-specific stuff, so I'll just give this to you (don't worry, we'll get back to remix soon): @@ -1501,13 +1501,13 @@ Now we just need to run this file. We wrote it in TypeScript to get type safety 💿 Install `ts-node` and `tsconfig-paths` as dev dependencies: -```sh +```shellscript nonumber npm install --save-dev ts-node tsconfig-paths ``` 💿 And now we can run our `seed.ts` file with that: -```sh +```shellscript nonumber npx ts-node --require tsconfig-paths/register prisma/seed.ts ``` @@ -1542,32 +1542,30 @@ This works just fine, but the problem is, during development, we don't want to c So we've got a bit of extra work to do to avoid this development time problem. -Note that this isn't a remix-only problem. Any time you have "live reload" of server code, you're going to have to either disconnect and reconnect to databases (which can be slow) or do the workaround I'm about to show you. +Note that this isn't a remix-only problem. Any time you have "live reload" of server code, you're going to have to either disconnect and reconnect to databases (which can be slow) or use the [`global` singleton workaround][global-singleton-workaround]. -💿 Copy this into a new file called `app/utils/db.server.ts` +💿 Copy this into two new files called `app/utils/singleton.server.ts` & `app/utils/db.server.ts` + +```ts filename=app/utils/singleton.server.ts +export const singleton = ( + name: string, + valueFactory: () => Value +): Value => { + const g = global as any; + g.__singletons ??= {}; + g.__singletons[name] ??= valueFactory(); + return g.__singletons[name]; +}; +``` ```ts filename=app/utils/db.server.ts import { PrismaClient } from "@prisma/client"; -let db: PrismaClient; +import { singleton } from "./singleton.server"; -declare global { - var __db__: PrismaClient | undefined; -} - -// This is needed because in development we don't want to restart -// the server with every change, but we want to make sure we don't -// create a new connection to the DB with every change either. -// In production, we'll have a single connection to the DB. -if (process.env.NODE_ENV === "production") { - db = new PrismaClient(); -} else { - if (!global.__db__) { - global.__db__ = new PrismaClient(); - } - db = global.__db__; - db.$connect(); -} +// Hard-code a unique key, so we can look up the client when this module gets re-imported +const db = singleton("prisma", () => new PrismaClient()); +db.$connect(); export { db }; ``` @@ -2199,7 +2197,7 @@ With that updated, let's go ahead and reset our database to this schema: 💿 Run this: -```sh +```shellscript nonumber npx prisma db push ``` @@ -2291,7 +2289,7 @@ function getJokes() { 💿 Great, now run the seed again: -```sh +```shellscript nonumber npx prisma db seed ``` @@ -2312,13 +2310,13 @@ So our authentication will be of the traditional username/password variety. We'l 💿 Go ahead and get that installed right now, so we don't forget: -```sh +```shellscript nonumber npm install bcryptjs ``` 💿 The `bcryptjs` library has TypeScript definitions in DefinitelyTyped, so let's install those as well: -```sh +```shellscript nonumber npm install --save-dev @types/bcryptjs ``` @@ -4029,7 +4027,7 @@ function Document({ {title} @@ -4169,7 +4167,7 @@ With that understanding, we're going to add a `isRouteErrorResponse` check to th - `app/root.tsx` - Just as a last resort fallback. - `app/routes/jokes.$jokeId.tsx` - When a user tries to access a joke that doesn't exist (404). - `app/routes/jokes.new.tsx` - When a user tries to go to this page without being authenticated (401). Right now they'll just get redirected to the login if they try to submit it without authenticating. That would be super annoying to spend time writing a joke only to get redirected. Rather than inexplicably redirecting them, we could render a message that says they need to authenticate first. -- `app/routes/jokes._index.tsx` - If there are no jokes in the database then a random joke is 404-not found. (simulate this by deleting the `prisma/dev.db` and running `npx prisma db push`. Don't forget to run `npx prisma db seed` afterwards to get your seed data back.) +- `app/routes/jokes._index.tsx` - If there are no jokes in the database then a random joke is 404-not found. (simulate this by deleting the `prisma/dev.db` and running `npx prisma db push`. Don't forget to run `npx prisma db seed` afterward to get your seed data back.) 💿 Let's add these `isRouteErrorResponse` checks to the routes. @@ -4216,7 +4214,7 @@ function Document({ {title} @@ -4943,7 +4941,7 @@ function Document({