a first look at redwoodJS part 3 - prisma migrate, railway

ajcwebdev - Jun 23 '20 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

Initialize project.

railway init
Enter fullscreen mode Exit fullscreen mode

Give your project a name.

āœ” Create new Project
āœ” Enter project name: ajcwebdev-redwood
āœ” Environment: production
šŸŽ‰ Created project ajcwebdev-redwood
Enter fullscreen mode Exit fullscreen mode

Provision PostgreSQL

Add a plugin to your Railway project.

railway add
Enter fullscreen mode Exit fullscreen mode

Select PostgreSQL.

āœ” Plugin: postgresql 
šŸŽ‰ Created plugin postgresql
Enter fullscreen mode Exit fullscreen mode

Set environment variable

Create a .env file with your DATABASE_URL.

echo DATABASE_URL=`railway variables get DATABASE_URL` > .env
Enter fullscreen mode Exit fullscreen mode

Use the Railway Dashboard

01-

āŒ˜ + K

02-

Provision PostgreSQL

03-

Select PostgreSQL on left sidebar

04-

Click Connect

05-

Copy paste your database URL (this db was deleted)

06-

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?
}
Enter fullscreen mode Exit fullscreen mode

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())
}
Enter fullscreen mode Exit fullscreen mode

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()
  })
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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...
Enter fullscreen mode Exit fullscreen mode

Look at all the stuff I'm not doing! Open the browser and enter localhost:8910/posts.

07-posts-page

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.

08-new-post-form

We were taken to a new route, /posts/new. Let's create a blog post about everyone's favorite dinosaur.

09-new-deno-post

If we click the save button we are brought back to the posts page.

10-deno-post-created

We now have a table with our first post.

11-deno-post-edit

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:

12-fauna-post

And one more:

13-next-post

14-all-posts

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
Enter fullscreen mode Exit fullscreen mode

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!
}
Enter fullscreen mode Exit fullscreen mode

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 },
  })
}
Enter fullscreen mode Exit fullscreen mode

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'],
})
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .