Several weeks ago I had a vision. A vision of easier access to data. Simpler syntax to access my database. Today, that vision becomes reality.
With the release of 0.39/1.0.0-rc.1 we’re including an experimental package called RedwoodRecord. It’s modeled off of ActiveRecord from Ruby on Rails. It’s an ORM that wraps Prisma and provides class-based access to your data. We’re calling it “experimental” because we don’t really know how the community is going to react to it, and the API isn’t fully baked. We’re trying some things out and need feedback to see where we should go with this library.
You can check out the official docs here, but here’s a quick guide to getting started.
First, add to your package.json:
yarn add -W api @redwoodjs/record
Next, generate a “model” class which will represent a single table in your database:
yarn rw g model User
(Take a look at api/src/models/index.js
to see what was added.)
You now have a model at api/src/models/User.js
. Before we can start using it, we need to run one more command which will turn your schema.prisma file into a cached JSON file, and create the index.js file in the models directory, adding a bit of config for the models:
yarn rw record init
For now you’ll need to run this command any time you change your schema, or after creating new models.
Now, you can update your users service to use your model instead of raw Prisma:
// api/src/services/users/users.js
import { User } from 'src/models'
export const users = () => {
return User.all()
}
export const user = ({ id }) => {
return User.find(id)
}
export const createUser = async ({ input }) => {
return User.create(input, { throw: true })
}
export const updateUser = async ({ id, input }) => {
const user = await User.find(id)
return user.update(input, { throw: true })
}
export const deleteUser = async ({ id }) => {
const user = await User.find(id)
return user.destroy({ throw: true })
}
Since models are just classes, you can add your own functions/properties to the class and they’ll be available anywhere a user is. Let’s say you store firstName
and lastName
separately in your database, but you also want to display a fullName
. Rather than write a function somewhere else and import it when needed, just add it to User
:
// api/src/models/User.js
import { RedwoodRecord } from '@redwoodjs/record'
export default class User extends RedwoodRecord {
get fullName() {
return `${this.firstName} ${this.lastName}`
}
}
And now whenever you want someone’s full name:
const user = User.find(123)
user.fullName
You can also replace the object returned in getCurrentUser
to be an instance of your model (dbAuth is used in this example, so session
is just an object containing the id
of the logged in user):
// api/src/lib/auth.js
export const getCurrentUser = async (session) => {
return await User.find(session.id, { select: { id: true, email: true } })
}
Now, currentUser
on the api side will be an instance of RedwoodRecord, so you can get to related models directly through your user. Here, we’ve also created a Post
model and now we can get only those posts that the current user owns:
// api/src/services/posts/posts.js
export const posts = async () => {
return context.currentUser.posts.all()
}
export const post = ({ id }) => {
return context.currentUser.posts.find(id)
}
export const createPost = ({ input }) => {
return context.currentUser.posts.create(input, { throw: true })
}
export const updatePost = async ({ id, input }) => {
const post = await context.currentUser.posts.find(id)
return post.update(input, { throw: true })
}
export const deletePost = async ({ id }) => {
const post = await context.currentUser.posts.find(id)
return post.destroy({ throw: true })
}
Start playing with it and let us know what you think! And check out the full docs for more syntax: RedwoodRecord | RedwoodJS Docs
Caveats
RedwoodRecord is not currently typed. The attributes on a model are created dynamically based on the object returned by Prisma, so I’m not even sure if it can be typed in its current iteration. If you have any insight into helping add Typescript to this library, get in touch!