What I wanted was to codify and standardize the types of things that we were already doing and just remove choice and remove friction and just give people the ability to sit down and say:
"All right, I know these technologies already; I have the prerequisite knowledge to do this."
Tom Preston-Werner - Full Stack Radio
Part 3 - Prisma Migrate, Railway
- Part 1 - created our first RedwoodJS application and pages
- Part 2 - created links to our different page routes and a reusable layout for our site
In this part we'll get our database up and running and learn to create, retrieve, update, and destroy (CRUD) blog posts.
3.1 Railway
Railway is an infrastructure provider that allows you to develop code in the cloud and deploy from anywhere. When you run apps on Railway, configs are intelligently provided to you based on your project and environment. We can spin up our database with the Railway CLI or the Railway dashboard and I will demonstrate both. First you need to create a Railway account.
Use the Railway CLI
Install the Railway CLI.
railway login
Initialize project.
railway init
Give your project a name.
ā Create new Project
ā Enter project name: ajcwebdev-redwood
ā Environment: production
š Created project ajcwebdev-redwood
Provision PostgreSQL
Add a plugin to your Railway project.
railway add
Select PostgreSQL.
ā Plugin: postgresql
š Created plugin postgresql
Set environment variable
Create a .env
file with your DATABASE_URL.
echo DATABASE_URL=`railway variables get DATABASE_URL` > .env
Use the Railway Dashboard
ā + K
Provision PostgreSQL
Select PostgreSQL on left sidebar
Click Connect
Copy paste your database URL (this db was deleted)
Set environment variable
Open your .env
file. Copy the environment variable from the Railway dashboard and set it in place of YOUR_URL_HERE
in the below example.
DATABASE_URL=YOUR_URL_HERE
3.2 schema.prisma
So far we've been working in the web
folder. In our api
folder there is a folder called db
for our Prisma schema.
āāā api
āāā db
āāā schema.prisma
āāā seeds.js
Prisma is an ORM that provides a type-safe API for submitting database queries which return JavaScript objects. It was selected by Tom in the hopes of emulating Active Record's role in Ruby on Rails.
The Prisma schema file is the main configuration file for your Prisma setup. It is typically called schema.prisma
// api/db/schema.prisma
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
binaryTargets = "native"
}
model UserExample {
id Int @id @default(autoincrement())
email String @unique
name String?
}
In order to set up Prisma Client, you need a Prisma schema file with:
- your database connection
- the Prisma Client
generator
- at least one
model
Change the database provider from sqlite
to postgresql
and delete the default UserExample
model. Make a Post
model with an id
, title
, body
, and createdAt
time.
// api/db/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
binaryTargets = "native"
}
model Post {
id Int @id @default(autoincrement())
title String
body String
createdAt DateTime @default(now())
}
seeds.js
seeds.js
is used to populate your database with any data that needs to exist for your app to run at all (maybe an admin user or site configuration).
// api/db/seeds.js
const { PrismaClient } = require('@prisma/client')
const dotenv = require('dotenv')
dotenv.config()
const db = new PrismaClient()
async function main() {
console.warn('Please define your seed data.')
// return Promise.all(
// data.map(async (user) => {
// const record = await db.user.create({
// data: { name: user.name, email: user.email },
// })
// console.log(record)
// })
// )
}
main()
.catch((e) => console.error(e))
.finally(async () => {
await db.$disconnect()
})
3.3 redwood prisma migrate
Running yarn rw prisma migrate dev
generates the folders and files necessary to create a new migration. We will name our migration nailed-it
.
yarn rw prisma migrate dev --name nailed-it
Running Prisma CLI:
yarn prisma migrate dev --name nailed-it --preview-feature --schema "/Users/ajcwebdev/projects/ajcwebdev-redwood/api/db/schema.prisma"
Prisma schema loaded from db/schema.prisma
Datasource "DS": PostgreSQL database "railway", schema "public" at "containers-us-west-1.railway.app:5884"
The following migration(s) have been applied:
migrations/
āā 20210307113114_nailed_it/
āā migration.sql
ā Generated Prisma Client (2.16.1) to ./../node_modules/@prisma/client in 89ms
Everything is now in sync.
The migrate
command creates and manages database migrations. It can be used to create, apply, and rollback database schema updates in a controlled manner.
āāā api
āāā db
āāā migrations
ā āāā 20210307113114_nailed_it
ā ā āāā migration.sql
ā āāā migration_lock.toml
āāā schema.prisma
āāā seeds.js
migration.sql
CREATE TABLE "Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
3.4 redwood generate scaffold
A scaffold quickly creates a CRUD interface for a model by generating all the necessary files and corresponding routes.
yarn rw g scaffold post
This will generate pages, SDL's, services, layouts, cells, and components based on a given database schema Model.
ā Generating scaffold files...
ā Successfully wrote file `./web/src/components/Post/EditPostCell/EditPostCell.js`
ā Successfully wrote file `./web/src/components/Post/Post/Post.js`
ā Successfully wrote file `./web/src/components/Post/PostCell/PostCell.js`
ā Successfully wrote file `./web/src/components/Post/PostForm/PostForm.js`
ā Successfully wrote file `./web/src/components/Post/Posts/Posts.js`
ā Successfully wrote file `./web/src/components/Post/PostsCell/PostsCell.js`
ā Successfully wrote file `./web/src/components/Post/NewPost/NewPost.js`
ā Successfully wrote file `./api/src/graphql/posts.sdl.js`
ā Successfully wrote file `./api/src/services/posts/posts.js`
ā Successfully wrote file `./api/src/services/posts/posts.scenarios.js`
ā Successfully wrote file `./api/src/services/posts/posts.test.js`
ā Successfully wrote file `./web/src/scaffold.css`
ā Successfully wrote file `./web/src/layouts/PostsLayout/PostsLayout.js`
ā Successfully wrote file `./web/src/pages/Post/EditPostPage/EditPostPage.js`
ā Successfully wrote file `./web/src/pages/Post/PostPage/PostPage.js`
ā Successfully wrote file `./web/src/pages/Post/PostsPage/PostsPage.js`
ā Successfully wrote file `./web/src/pages/Post/NewPostPage/NewPostPage.js`
ā Adding layout import...
ā Adding set import...
ā Adding scaffold routes...
ā Adding scaffold asset imports...
Look at all the stuff I'm not doing! Open the browser and enter localhost:8910/posts
.
We have a new page called Posts with a button to create a new post. If we click the new post button we are given an input form with fields for title and body.
We were taken to a new route, /posts/new
. Let's create a blog post about everyone's favorite dinosaur.
If we click the save button we are brought back to the posts page.
We now have a table with our first post.
When we click the edit button we are taken to a route for the individual post that we want to edit. Each post has a unique id.
Lets add another blog post:
And one more:
3.5 api/src
We've seen the prisma
folder under api
, now we'll look at the src
folder containing our GraphQL code. Redwood comes with GraphQL integration built in to make it easy to get our client talking to our serverless functions.
āāā api
āāā src
āāā functions
ā āāā graphql.js
āāā graphql
ā āāā posts.sdl.js
āāā lib
ā āāā auth.js
ā āāā db.js
ā āāā logger.js
āāā services
āāā posts
āāā posts.js
āāā posts.scenarios.js
āāā posts.test.js
3.6 posts.sdl.js
GraphQL schemas for a service are specified using the GraphQL (schema definition language) which defines the API interface for the client. Our schema
has five object types each with their own fields and types.
# api/src/graphql/posts.sdl.js
type Post {
id: Int!
title: String!
body: String!
createdAt: DateTime!
}
type Query {
posts: [Post!]!
post(id: Int!): Post
}
input CreatePostInput {
title: String!
body: String!
}
input UpdatePostInput {
title: String
body: String
}
type Mutation {
createPost(input: CreatePostInput!): Post!
updatePost(id: Int!, input: UpdatePostInput!): Post!
deletePost(id: Int!): Post!
}
3.7 posts.js
services
contain business logic related to your data. A service implements the logic of talking to the third-party API. This is where your code for querying or mutating data with GraphQL ends up. Redwood will automatically import and map resolvers from the services
file onto your SDL.
// api/src/services/posts/posts.js
import { db } from 'src/lib/db'
import { requireAuth } from 'src/lib/auth'
export const beforeResolver = (rules) => {
rules.add(requireAuth)
}
export const posts = () => {
return db.post.findMany()
}
export const post = ({ id }) => {
return db.post.findUnique({
where: { id },
})
}
export const createPost = ({ input }) => {
return db.post.create({
data: input,
})
}
export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id },
})
}
export const deletePost = ({ id }) => {
return db.post.delete({
where: { id },
})
}
3.8 db.js
db.js
contains the code that instantiates the Prisma database client. This is the db
object that was imported in the services file.
// api/src/lib/db.js
import { PrismaClient } from '@prisma/client'
import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger'
import { logger } from './logger'
export const db = new PrismaClient({
log: emitLogLevels(['info', 'warn', 'error']),
})
handlePrismaLogging({
db,
logger,
logLevels: ['info', 'warn', 'error'],
})
In the next part we'll learn about Cells. We will set up our frontend to query data from our backend to render a list of our blog posts to the front page.