❗ This is not a tutorial ❗
In https://zalgorithm.com/hello-remix/ I created a Remix app with npx create-remix@latest
. That creates a basic app, but doesn’t configure a database. This app doesn’t need a database but I’m going to set one up for fun. I’ll give these instructions a try: https://remix.run/docs/en/main/tutorials/jokes#database.
SQLite and Prisma
SQLight is the database. Prisma is the ORM. Here it goes:
$ npm install --save-dev prisma
$ npm install @prisma/client
Now initialize (???) Prisma with SQLight:
$ npx prisma init --datasource-provider sqlite
The above command creates a prisma/schema.prisma
file:
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
I’m unsure about the url
property. I see that in the .env
file that’s been generated for the app, DATABASE_URL
is set to "file:./dev.db"
. When I push the changes to my production site, do I just create a .env
file with something like this?
DATABASE_URL="file:./prod.db"
???
(Edit: yes, it looks like I can call it anything I want: https://www.prisma.io/docs/orm/overview/databases/sqlite.)
I’m getting ahead of myself. The next step is to add a model. I won’t use the Joke
model from the tutorial I’m following.
I’ll give this a try:
model Blog {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
description String
url String
}
$ npx prisma db push
# output:
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"
SQLite database dev.db created at file:./dev.db
🚀 Your database is now in sync with your Prisma schema. Done in 18ms
✔ Generated Prisma Client (v5.10.2) to ./node_modules/@prisma/client in 54ms
To avoid possible git issues, I’m going to delete prisma/dev.db
, add /prisma/dev.db
to my .gitignore
file, then run npx prisma db push
again. (Done, but probably unnecessary.)
Seed the Database
The Blog
model is an idea for my next project. For now, I just want to get Remix working with SQLite in a production environment. I’ll seed the database with something:
touch prisma/seed.ts
// prisma/seed.ts
import { PrismaClient } from "@prisma/client";
const db = new PrismaClient();
async function seed() {
await Promise.all(
getBlogs().map((blog) => {
return db.blog.create({ data: blog });
})
);
}
seed();
function getBlogs() {
return [
{
title: "Amateur Web Development",
description: "Stake your claim on the internet.",
url: "https://amateur.example.com",
},
{
title: "Other ways of knowing",
description: "You've got this.",
url: "https://otherways.example.com",
},
{
title: "Algorithm Shmalgorithm",
description: "Free yourself from the machine",
url: "https://zalgorithm.com",
},
];
}
Now I need to run the file. This is where I got stuck when initially following the full Joke tutorial. The instructions are to install ts-node
and tsconfig-paths
as dev dependencies:
$ npm install --save-dev ts-node tsconfig-paths
Then run the seed.ts
file with:
$ npx ts-node --require tsconfig-paths/register prisma/seed.ts
But… that returns the following error:
TypeError: Unknown file extension ".ts" for /home/scossar/remix/starting-over/hello_world/prisma/seed.ts
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:160:9)
at defaultGetFormat (node:internal/modules/esm/get_format:203:36)
at defaultLoad (node:internal/modules/esm/load:141:22)
at async ModuleLoader.load (node:internal/modules/esm/loader:409:7)
at async ModuleLoader.moduleProvider (node:internal/modules/esm/loader:291:45)
at async link (node:internal/modules/esm/module_job:76:21) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
I don’t remember how I fixed it, but I posted the solution on the Remix Discord server a few months ago. Maybe I can find that post. https://discord.com/channels/770287896669978684/1178260789665218590. I figured it out from this: https://github.com/prisma/prisma/issues/7053#issuecomment-1563824382.
$ npm i -D tsx
# then add a "prisma" entry to package.json:
"prisma": {
"seed": "tsx prisma/seed.ts"
}
# then
$ npx prisma db seed
# returns
Environment variables loaded from .env
Running seed command `tsx prisma/seed.ts` ...
🌱 The seed command has been executed.
There’s probably a better way. Also, do I still need the ts-node
and tsconfig-paths
dev dependencies? (With comments off on this blog, I’m asking _myself_ questions. Also, can the WordPress editor accept markdown?)
Prisma Studio
Run npx prisma studio
to view and edit the database:
Prisma Dev Multiple Connections Issue
The problem is that in development mode, @remix-run/serve
creates a new database connection every time the code is saved. To avoid that, use the global singleton workaround: https://remix.run/docs/en/main/guides/manual-mode#keeping-in-memory-server-state-across-rebuilds.
The basic idea makes sense:
// singleton.server.ts
export const singleton = <Value>(
name: string,
valueFactory: () => Value
): Value => {
const g = global as any;
g.__singletons ??= {};
g.__singletons[name] ??= valueFactory();
return g.__singletons[name];
};
<Value>
is a generic type parameter in TypeScript. It’s a way of defining a function that can work with any type, while still keeping type safety.In the
singleton
function,<Value>
is used to denote the type of the value that thevalueFactory
function returns, and also the type of the value that thesingleton
function itself returns.When you call the
singleton
function, you can specify what typeValue
should be. For example, if you’re creating a singleton that holds a number, you could call the function like this:const mySingleton = singleton<number>('mySingleton', () => 42);
In this case, TypeScript will know that
mySingleton
is a number.If you don’t specify a type when calling the function, TypeScript will try to infer it from the
valueFactory
argument. For example:const mySingleton = singleton('mySingleton', () => 42);
In this case, TypeScript will infer that
Value
isnumber
, because thevalueFactory
function returns a number.By using a generic type parameter, the
GitHub Copilot 🙁singleton
function can create singletons of any type, while still providing type safety.
Using the Data
I’ll display the blog list at /blogs
:
$ touch app/routes/blogs.tsx
// app/routes/blogs.tsx
export default function Blogs() {
return (
<div>
<h1>Blogs</h1>
</div>
);
}
That gives me:
Now display the blogs:
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { db } from "~/utils/db.server";
export const loader = async () => {
return json({
blogs: await db.blog.findMany(),
});
};
export default function Blogs() {
const data = useLoaderData<typeof loader>();
return (
<div>
<h1>Blogs</h1>
<ul>
{data.blogs.map((blog) => (
<li key={blog.id}>
<a href={blog.url}>{blog.title}</a>
</li>
))}
</ul>
</div>
);
}
Great! Except subdomains of example.com
return a “page not found” error. I’ll update those URLs with Prisma Studio. Also, I don’t _think_ I’ve set title
, description
and url
as required fields on the model. Why isn’t typescript giving me an error?
Push the Changes to Production
$ git status
On branch main
nothing to commit, working tree clean
$ git push live main
Enumerating objects: 28, done.
Counting objects: 100% (28/28), done.
Delta compression using up to 8 threads
Compressing objects: 100% (21/21), done.
Writing objects: 100% (22/22), 7.86 KiB | 731.00 KiB/s, done.
Total 22 (delta 9), reused 0 (delta 0), pack-reused 0
To ssh://<redacted>/repo/site.git
$ ssh <redacted path to my repo>
$ cd /var/www/hello_zalgorithm_com
$ touch .env
$ emacs .env
# .env
DATABASE_URL="file:./prod.db"
then:
$ npm install
$ npx prisma db push
$ npx prisma db seed
I _think_ I need to run the build
command:
$ npm run build
> build
> remix build
info building... (NODE_ENV=production)
info built (1.5s)
$ pm2 list
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼──────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ npm start │ default │ N/A │ fork │ 31109 │ 24h │ 1 │ online │ 0% │ 51.8mb │ scossar │ disabled
$ pm2 reload 0
Success: https://hello.zalgorithm.com/blogs (these links will be broken soon 🙁 )