Building a Habit Tracker with Prisma, Chakra UI, and React
In June 2019, Prisma 2 Preview was released. Prisma 1 changed the way we interact with databases. We could access databases through plain JavaScript methods and objects without having to write the query in the database language itself. Prisma 1 acted as an abstraction in front of the database so it was easier to make CRUD (create, read, update and delete) applications.
Prisma 1 architecture looked like this:
Notice that there’s an additional Prisma server required for the back end to access the database. The latest version doesn’t require an additional server. It's called The Prisma Framework (formerly known as Prisma 2) which is a complete rewrite of Prisma. The original Prisma was written in Scala, so it had to be run through JVM and needed an additional server to run. It also had memory issues.
The Prisma Framework is written in Rust so the memory footprint is low. Also, the additional server required while using Prisma 1 is now bundled with the back end, so you can use it just like a library.
The Prisma Framework consists of three standalone tools:
- Photon: a type-safe and auto-generated database client ("ORM replacement")
- Lift: a declarative migration system with custom workflows
- Studio: a database IDE that provides an Admin UI to support various database workflows.
Photon is a type-safe database client that replaces traditional ORMs, and Lift allows us to create data models declaratively and perform database migrations. Studio allows us to perform database operations through a beautiful Admin UI.
Why use Prisma?
Prisma removes the complexity of writing complex database queries and simplifies database access in the application. By using Prisma, you can change the underlying databases without having to change each and every query. It just works. Currently, it only supports mySQL, SQLite and PostgreSQL.
Prisma provides type-safe database access provided by an auto-generated Prisma client. It has a simple and powerful API for working with relational data and transactions. It allows visual data management with Prisma Studio.
Providing end-to-end type-safety means developers can have confidence in their code, thanks to static analysis and compile-time error checks. The developer experience increases drastically when having clearly defined data types. Type definitions are the foundation for IDE features — like intelligent auto-completion or jump-to-definition.
Prisma unifies access to multiple databases at once (coming soon) and therefore drastically reduces complexity in cross-database workflows (coming soon).
It provides automatic database migrations (optional) through Lift, based on a declarative datamodel expressed using GraphQL's schema definition language (SDL).
Prerequisites
For this tutorial, you need a basic knowledge of React. You also need to understand React Hooks.
Since this tutorial is primarily focused on Prisma, it’s assumed that you already have a working knowledge of React and its basic concepts.
If you don’t have a working knowledge of the above content, don't worry. There are tons of tutorials available that will prepare you for following this post.
Throughout the course of this tutorial, we’ll be using yarn
. If you don’t have yarn
already installed, install it from here.
To make sure we’re on the same page, these are the versions used in this tutorial:
- Node v12.11.1
- npm v6.11.3
- npx v6.11.3
- yarn v1.19.1
- prisma2 v2.0.0-preview016.2
- react v16.11.0
Folder Structure
Our folder structure will be as follows:
streaks-app/
client/
server/
The client/
folder will be bootstrapped from create-react-app while the server/
folder will be bootstrapped from prisma2 CLI.
So you just need to create a root folder called streaks-app/
and the subfolders will be generated while scaffolding it with the respective CLIs. Go ahead and create the streaks-app/
folder and cd
into it as follows:
$ mkdir streaks-app && cd $_
The Back End (Server Side)
Bootstrap a new Prisma 2 project
You can bootstrap a new Prisma 2 project by using the npx command as follows:
$ npx prisma2 init server
Alternatively, you can install prisma2
CLI globally and run the init
command. The do the following:
$ yarn global add prisma2 // or npm install --global prisma2
$ prisma2 init server
Run the interactive prisma2 init
flow & select boilerplate
Select the following in the interactive prompts:
- Select Starter Kit
- Select JavaScript
- Select GraphQL API
- Select SQLite
Once terminated, the init
command will have created an initial project setup in the server/
folder.
Now open the schema.prisma
file and replace it with the following:
generator photon {
provider = "photonjs"
}
datasource db {
provider = "sqlite"
url = "file:dev.db"
}
model Habit {
id String @default(cuid()) @id
name String @unique
streak Int
}
schema.prisma
contains the data model as well as the configuration options.
Here, we specify that we want to connect to the SQLite datasource called dev.db
as well as target code generators like photonjs
generator.
Then we define the data model Habit
, which consists of id
, name
and streak
.
id
is a primary key of type String
with a default value of cuid().
name
is of type String
, but with a constraint that it must be unique.
streak
is of type Int
.
The seed.js
file should look like this:
const { Photon } = require('@generated/photon')
const photon = new Photon()
async function main() {
const workout = await photon.habits.create({
data: {
name: 'Workout',
streak: 49,
},
})
const running = await photon.habits.create({
data: {
name: 'Running',
streak: 245,
},
})
const cycling = await photon.habits.create({
data: {
name: 'Cycling',
streak: 77,
},
})
const meditation = await photon.habits.create({
data: {
name: 'Meditation',
streak: 60,
},
})
console.log({
workout,
running,
cycling,
meditation,
})
}
main()
.catch(e => console.error(e))
.finally(async () => {
await photon.disconnect()
})
This file creates all kinds of new habits and adds it to the SQLite database.
Now go inside the src/index.js
file and remove its contents. We'll start adding content from scratch.
First go ahead and import the necessary packages and declare some constants:
const { GraphQLServer } = require('graphql-yoga')
const {
makeSchema,
objectType,
queryType,
mutationType,
idArg,
stringArg,
} = require('nexus')
const { Photon } = require('@generated/photon')
const { nexusPrismaPlugin } = require('nexus-prisma')
Now let’s declare our Habit
model just below it:
const Habit = objectType({
name: 'Habit',
definition(t) {
t.model.id()
t.model.name()
t.model.streak()
},
})
We make use of objectType
from the nexus
package to declare Habit
.
The name
parameter should be the same as defined in the schema.prisma
file.
The definition
function lets you expose a particular set of fields wherever Habit
is referenced. Here, we expose id
, name
and streak
field.
If we expose only the id
and name
fields, only those two will get exposed wherever Habit
is referenced.
Below that, paste the Query
constant:
const Query = queryType({
definition(t) {
t.crud.habit()
t.crud.habits()
// t.list.field('habits', {
// type: 'Habit',
// resolve: (_, _args, ctx) => {
// return ctx.photon.habits.findMany()
// },
// })
},
})
We make use of queryType
from the nexus
package to declare Query
.
The Photon generator generates an API that exposes CRUD functions on the Habit
model. This is what allows us to expose t.crud.habit()
and t.crud.habits()
method.
t.crud.habit()
allows us to query any individual habit by its id
or by its name
. t.crud.habits()
simply returns all the habits.
Alternatively, t.crud.habits()
can also be written as:
t.list.field('habits', {
type: 'Habit',
resolve: (_, _args, ctx) => {
return ctx.photon.habits.findMany()
},
})
Both the above code and t.crud.habits()
will give the same results.
In the above code, we make a field named habits
. The return type
is Habit
. We then call ctx.photon.habits.findMany()
to get all the habits from our SQLite database.
Note that the name of the habits
property is auto-generated using the pluralize package. It's therefore recommended practice to name our models singular — that is, Habit
and not Habits
.
We use the findMany
method on habits
, which returns a list of objects. We find all the habits
as we have mentioned no condition inside of findMany
. You can learn more about how to add conditions inside of findMany
here.
Below Query
, paste Mutation
as follows:
const Mutation = mutationType({
definition(t) {
t.crud.createOneHabit({ alias: 'createHabit' })
t.crud.deleteOneHabit({ alias: 'deleteHabit' })
t.field('incrementStreak', {
type: 'Habit',
args: {
name: stringArg(),
},
resolve: async (_, { name }, ctx) => {
const habit = await ctx.photon.habits.findOne({
where: {
name,
},
})
return ctx.photon.habits.update({
data: {
streak: habit.streak + 1,
},
where: {
name,
},
})
},
})
},
})
Mutation
uses mutationType
from the nexus
package.
The CRUD API here exposes createOneHabit
and deleteOneHabit
.
createOneHabit
, as the name suggests, creates a habit whereas deleteOneHabit
deletes a habit.
createOneHabit
is aliased as createHabit
, so while calling the mutation we call createHabit
rather than calling createOneHabit
.
Similarly, we call deleteHabit
instead of deleteOneHabit
.
Finally, we create a field named incrementStreak
, which increments the streak of a habit. The return type
is Habit
. It takes an argument name
as specified in the args
field of type String
. This argument is received in the resolve
function as the second argument. We find the habit
by calling ctx.photon.habits.findOne()
while passing in the name
parameter in the where
clause. We need this to get our current streak
. Then finally we update the habit
by incrementing the streak
by 1.
Below Mutation
, paste the following:
const photon = new Photon()
new GraphQLServer({
schema: makeSchema({
types: [Query, Mutation, Habit],
plugins: [nexusPrismaPlugin()],
}),
context: { photon },
}).start(() =>
console.log(
`🚀 Server ready at: http://localhost:4000\n⭐️ See sample queries: http://pris.ly/e/js/graphql#5-using-the-graphql-api`,
),
)
module.exports = { Habit }
We use the makeSchema
method from the nexus
package to combine our model Habit
, and add Query
and Mutation
to the types
array. We also add nexusPrismaPlugin
to our plugins
array. Finally, we start our server at localhost:4000. Port 4000 is the default port for graphql-yoga. You can change the port as suggested here.
Let's start the server now. But first, we need to make sure our latest schema changes are written to the node_modules/@generated/photon
directory. This happens when you run prisma2 generate
.
If you haven't installed prisma2
globally, you'll have to replace prisma2 generate
with ./node_modules/.bin/prisma2 generate
. Then we need to migrate our database to create tables.
The post Building a Habit Tracker with Prisma, Chakra UI, and React appeared first on SitePoint.
Comments
Post a Comment