The Prisma Framework (formerly known as Prisma 2) is a complete rewrite of the original Prisma. It is being rewritten in Rust while the original was written in Scala. The original version had memory issues with it and it required JVM to run. It also needed an additional server to run in addition to a backend server. The newest version of Prisma does not require any such thing. With The Prisma Framework, the query engine is now a bundled executable that is run alongside the backend on the same server.
The Prisma Framework consists of 3 standalone tools to tackle the problems of data access, data migrations, and admin UI:
- Photon: Type-safe and auto-generated database client ("ORM replacement")
- Lift: Declarative migration system with custom workflows
- Studio: Provides Admin UI to support various database workflows
So now let's get started with building a server with The Prisma Framework.
To keep it fun and corny, we will be making a Dad Jokes App.
Prerequisites
For this tutorial, you need a basic knowledge of React Native. You also need to understand React Hooks.
Since this tutorial is primarily focused on Prisma, it is assumed that you already have a working knowledge of React and its basic concepts.
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.12.0
- npm v6.11.3
- npx v6.11.3
- yarn v1.19.1
- prisma2 v2.0.0-preview016.2
- expo-cli v3.7.1
- expo v35.0.0
Server-Side (The Prisma Framework)
Start a new Prisma 2 project
Install prisma2
CLI globally and run the init
command then:
$ 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 Joke {
id String @default(cuid()) @id
joke String @unique
}
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 Joke
which consists of id
and joke
.
id
is a primary key of type String
with a default value of cuid().
joke
is of type String
but with a constraint that it must be unique.
Open seed.js
file and paste the following:
const { Photon } = require('@generated/photon')
const photon = new Photon()
async function main() {
const joke1 = await photon.jokes.create({
data: {
joke:
'Did you hear the one about the guy with the broken hearing aid? Neither did he.',
},
})
const joke2 = await photon.jokes.create({
data: {
joke:
'My dog used to chase people on a bike a lot. It got so bad I had to take his bike away.',
},
})
const joke3 = await photon.jokes.create({
data: {
joke: "I don't trust stairs. They're always up to something.",
},
})
const joke4 = await photon.jokes.create({
data: {
joke:
"Dad died because he couldn't remember his blood type. I will never forget his last words. Be positive.",
},
})
console.log({ joke1, joke2, joke3, joke4 })
}
main()
.catch(e => console.error(e))
.finally(async () => {
await photon.disconnect()
})
We are basically adding jokes
into our SQLite database.
Now go inside src/index.js
file and remove the contents of it. 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')
We have declared a constant photon
which instantiates a new Photon
class.
Let us declare our Joke
model. Paste the code below it:
const Joke = objectType({
name: 'Joke',
definition(t) {
t.model.id()
t.model.joke()
},
})
We make use of objectType
from the nexus
package to declare Joke
.
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 Joke
is referenced. Here, we expose id
and joke
field.
If we expose only joke
field, then id
will not get exposed and only joke
will get exposed wherever Joke
is referenced.
Below that paste the Query
constant:
const Query = queryType({
definition(t) {
t.crud.joke()
t.crud.jokes()
},
})
We make use of queryType
from the nexus
package to declare Query
.
The Photon generator generates an API that exposes CRUD functions on Joke
model. This is what allows us to expose t.crud.joke()
and t.crud.jokes()
method.
We can also write t.crud.jokes()
as follows:
t.list.field('jokes', {
type: 'Joke',
resolve: (_, _args, ctx) => {
return ctx.photon.jokes.findMany()
},
})
Both the above code and t.crud.jokes()
will give the same results.
In the above code, we make a field named jokes
. The return type
is Joke
. We then call ctx.photon.jokes.findMany()
to get all the jokes from our SQLite database.
Note that the name of the
jokes
property is auto-generated using the pluralize package. It is therefore recommended to name our models singular i.e.Joke
and notJokes
.
We use the findMany
method on jokes
which returns a list of objects. We find all the jokes
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.createOneJoke({ alias: 'createJoke' })
t.crud.deleteOneJoke({ alias: 'deleteJoke' })
},
})
Mutation
uses mutationType
from the nexus
package.
The CRUD API here exposes createOneJoke
and deleteOneJoke
.
createOneJoke
, as the name suggests, creates a joke whereas deleteOneJoke
deletes a joke.
createOneJoke
is aliased as createJoke
so while calling the mutation we call createJoke
rather than calling createOneJoke
.
Similarly, we call deleteJoke
instead of deleteOneJoke
.
Finally, put the following code below Mutation
:
const photon = new Photon()
new GraphQLServer({
schema: makeSchema({
types: [Query, Mutation, Joke],
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 = { Joke }
We use the makeSchema
method from the nexus
package to combine our model Quote
, add Query
and Mutation
to the types
array. We also add nexusPrismaPlugin
to our plugins
array. Finally, we start our server at http://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
. After that, we need to migrate our database to create tables.
Migrate your database with Lift
Migrating your database with Lift follows a 2-step process:
- Save a new migration (migrations are represented as directories on the file system)
- Run the migration (to migrate the schema of the underlying database)
In CLI commands, these steps can be performed as follows (the CLI steps are in the process of being updated to match):
$ prisma2 lift save --name 'init'
$ prisma2 lift up
Now the migration process is done. We've successfully created the table. Now we can seed our database with initial values.
Go ahead and run the following command in the terminal:
$ yarn seed
This will seed our database with 8 habits as specified in our seed.js
file.
Now you can run the server by typing:
$ yarn dev
This will run your server at http://localhost:4000/ which you can open and query all the APIs you've made.
List all jokes
query jokes {
jokes {
id
joke
}
}
Find A Particular Joke
query joke {
joke(
where: {
joke: "Did you hear the one about the guy with the broken hearing aid? Neither did he."
}
) {
id
joke
}
}
Create A Joke
mutation createJoke {
createJoke(
data: { joke: "To the guy who invented zero... thanks for nothing." }
) {
id
joke
}
}
Delete a joke
mutation deleteJoke {
deleteJoke(where: { id: "ck2zqhwvo0001cav551f1me34" }) {
id
joke
}
}
This is all we need for the backend. Let's work on the frontend now.
Client-Side (React Native)
Bootstrap a new Expo project
Let’s set up a new Expo project using expo-cli
. Firstly, make sure to install it globally and then run the init
command:
$ yarn global add expo-cli
$ expo init DadJokes
Select the following in the interactive prompts:
- Select tabs
- Type name of the project to be DadJokes
- Press y to install dependencies with yarn
This should bootstrap a new React Native project using expo-cli
.
Now run the project by typing:
$ yarn start
Press i
to run the iOS Simulator. This will automatically run the iOS Simulator even if it’s not opened.
Press a
to run the Android Emulator. Note that the emulator must be installed and started already before typing a
. Otherwise, it will throw an error in the terminal.
It should look like this:
React Navigation
The initial setup has already installed react-navigation
for us. The bottom tab navigation also works by default because we chose tabs
in the second step of expo init
. You can check it by tapping on Links and Settings.
The screens/
folder is responsible for the content displayed when the tabs are changed.
Now, completely remove the contents of HomeScreen.js
and replace them with the following:
import React from 'react'
import { Text, View } from 'react-native'
class HomeScreen extends React.Component {
render() {
return (
<View>
<Text>Home Screen</Text>
</View>
)
}
}
export default HomeScreen
Now we’ll adapt the tabs according to the application we’re going to build. For our Dad Jokes app, we’re going to have 2 screens: Home
and Add Joke
.
We can completely delete LinksScreen.js
and SettingsScreen.js
from the screens/
folder. Notice our app breaks, with a red screen full of errors.
This is because we’ve linked to it in the navigation/
folder. Open MainTabNavigator.js
in the navigation/
folder. It currently looks like this:
import React from 'react';
import { Platform } from 'react-native';
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';
import TabBarIcon from '../components/TabBarIcon';
import HomeScreen from '../screens/HomeScreen';
import LinksScreen from '../screens/LinksScreen';
import SettingsScreen from '../screens/SettingsScreen';
const config = Platform.select({
web: { headerMode: 'screen' },
default: {},
});
const HomeStack = createStackNavigator(
{
Home: HomeScreen,
},
config
);
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? `ios-information-circle${focused ? '' : '-outline'}`
: 'md-information-circle'
}
/>
),
};
HomeStack.path = '';
const LinksStack = createStackNavigator(
{
Links: LinksScreen,
},
config
);
LinksStack.navigationOptions = {
tabBarLabel: 'Links',
tabBarIcon: ({ focused }) => (
<TabBarIcon focused={focused} name={Platform.OS === 'ios' ? 'ios-link' : 'md-link'} />
),
};
LinksStack.path = '';
const SettingsStack = createStackNavigator(
{
Settings: SettingsScreen,
},
config
);
SettingsStack.navigationOptions = {
tabBarLabel: 'Settings',
tabBarIcon: ({ focused }) => (
<TabBarIcon focused={focused} name={Platform.OS === 'ios' ? 'ios-options' : 'md-options'} />
),
};
SettingsStack.path = '';
const tabNavigator = createBottomTabNavigator({
HomeStack,
LinksStack,
SettingsStack,
});
tabNavigator.path = '';
export default tabNavigator;
Remove references to LinksStack
and SettingsStack
completely, because we don’t need these screens in our app. It should look like this:
import React from 'react'
import { Platform } from 'react-native'
import {
createBottomTabNavigator,
createStackNavigator,
} from 'react-navigation'
import TabBarIcon from '../components/TabBarIcon'
import HomeScreen from '../screens/HomeScreen'
const HomeStack = createStackNavigator({
Home: HomeScreen,
})
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? `ios-information-circle${focused ? '' : '-outline'}`
: 'md-information-circle'
}
/>
),
}
export default createBottomTabNavigator({
HomeStack,
})
Now reload the app to see the error gone.
Go ahead and create AddJokeScreen.js
inside the screens/
folder.
Add the following inside AddJokeScreen.js
:
import React from 'react'
import { Text, View } from 'react-native'
class AddJokeScreen extends React.Component {
render() {
return (
<View>
<Text>Add Joke Screen</Text>
</View>
)
}
}
export default AddJokeScreen
Open up MainTabNavigator.js
and import AddJokeScreen
at the top:
import AddJokeScreen from '../screens/AddJokeScreen'
Now go ahead and add the following code above our default export:
const AddJokeStack = createStackNavigator({
AddJoke: AddJokeScreen
})
AddJokeStack.navigationOptions = {
tabBarLabel: 'Add Joke',
tabBarIcon: ({ focused }) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? `ios-add-circle${focused ? '' : '-outline'}`
: 'md-add-circle'
}
/>
)
}
Also, change the default export to:
export default createBottomTabNavigator({
HomeStack,
AddJokeStack
})
Now you should see 2 screens: Home
and AddJoke
with their respective icons as follows:
We now need to get rid of the header that’s showing on each screen, taking up some top space. To get rid of it, we need to add headerMode: 'none'
in the createStackNavigator
config.
We need to add it to HomeStack
and AddJokeStack
.
HomeStack
should become:
const HomeStack = createStackNavigator(
{ Home: HomeScreen },
{ headerMode: 'none' }
)
AddJokeStack
should become:
const AddJokeStack = createStackNavigator(
{ AddJoke: AddJokeScreen },
{ headerMode: 'none' }
)
Now if you check the text goes up to the top-left right above the clock.
There’s an easy fix for this. We need to use SafeAreaView
. SafeAreaView
renders content within the safe area boundaries of a device. Let’s go into the screens/
directory and change HomeScreen.js
to use SafeAreaView
so that it looks like this:
import React from 'react'
import { SafeAreaView, Text } from 'react-native'
class HomeScreen extends React.Component {
render() {
return (
<SafeAreaView>
<Text>Home Screen</Text>
</SafeAreaView>
)
}
}
export default HomeScreen
It now renders the content inside the boundaries of the device.
Also, do it for AddJokeScreen
like so:
import React from 'react'
import { SafeAreaView, Text } from 'react-native'
class AddJokeScreen extends React.Component {
render() {
return (
<SafeAreaView>
<Text>Add Joke Screen</Text>
</SafeAreaView>
)
}
}
export default AddJokeScreen
It’s repetitive to wrap SafeAreaView
inside every component instead of setting it up on a root component like App.js
. But be aware that this won’t work if you try doing it on App.js
.
Remember, SafeAreaView
should always be set up on screen components or any content in them, and not wrap entire navigators. You can read more about it on this blog post.
GraphQL Queries and Mutations
Lets add GraphQL queries to our app which we triggered through the GraphiQL editor.
Inside components
folder, create a graphql
folder.
$ mkdir graphql && cd $_
Inside graphql
folder, create mutations
and queries
folder.
$ mkdir mutations queries
Inside queries
folder, create a file named jokes.js
.
$ cd queries
$ touch jokes.js
Inside jokes.js
, paste the following:
import { gql } from 'apollo-boost'
export const LIST_ALL_JOKES_QUERY = gql`
query jokes {
jokes {
id
joke
}
}
`
Notice that the above query
is similar to what we typed in the GraphiQL editor. This is how GraphQL is used. First, we type the query in the GraphiQL editor and see if it gives the data that we need and then we just copy-paste it into the application.
Inside mutations
folder, create 2 files createJoke.js
and deleteJoke.js
.
$ cd ../mutations
$ touch createJoke.js deleteJoke.js
Inside createJoke.js
, paste the following:
import { gql } from 'apollo-boost'
export const CREATE_JOKE_MUTATION = gql`
mutation createJoke($joke: String!) {
createJoke(data: { joke: $joke }) {
id
joke
}
}
`
Again we have copied the mutation
from our GraphiQL editor above. The main difference is we have replaced the hardcoded value with a variable so we can type in whatever user has specified.
Inside deleteJoke.js
, paste the following:
import { gql } from 'apollo-boost'
export const DELETE_JOKE_MUTATION = gql`
mutation deleteJoke($id: ID) {
deleteJoke(where: { id: $id }) {
id
joke
}
}
`
Now create 2 files in components/
folder namely Error.js
and Loading.js
.
$ cd ../../
$ touch Loading.js Error.js
In Error.js
, paste the following:
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { Text } from 'react-native-elements'
export const Error = () => (
<View>
<Text h3 h3Style={styles.error}>
Sorry, looks like we've run into an error
</Text>
</View>
)
const styles = StyleSheet.create({
error: {
color: 'red'
}
})
In Loading.js
, paste the following:
import React from 'react'
import { ActivityIndicator } from 'react-native'
export const Loading = () => <ActivityIndicator size='small' />
These components will be used later in the application.
Screens
Now that our navigation is taken care of, we can start working on the layout.
We’re going to be using a UI toolkit called React Native Elements. We will also be using Apollo Client to connect to our Prisma GraphQL backend.
So go ahead and install them:
$ yarn add react-native-elements @apollo/react-hooks apollo-boost graphql
Now open up App.js
and connect our client to the backend.
First, import the following:
import { ApolloProvider } from '@apollo/react-hooks'
import ApolloClient from 'apollo-boost'
Then right below it, create a constant:
const client = new ApolloClient({
uri: 'http://localhost:4000/'
})
The uri
inside of ApolloClient
is pointing out to Prisma GraphQL backend.
Then in the return wrap AppNavigator
with ApolloProvider
and pass in the client
:
<ApolloProvider client={client}>
<AppNavigator />
</ApolloProvider>
Now anything that will be inside of AppNavigator
can use Apollo Hooks.
Make sure your whole App.js
file looks like:
import { ApolloProvider } from '@apollo/react-hooks'
import { Ionicons } from '@expo/vector-icons'
import ApolloClient from 'apollo-boost'
import { AppLoading } from 'expo'
import { Asset } from 'expo-asset'
import * as Font from 'expo-font'
import React, { useState } from 'react'
import { Platform, StatusBar, StyleSheet, View } from 'react-native'
import AppNavigator from './navigation/AppNavigator'
const client = new ApolloClient({
uri: 'http://localhost:4000/'
})
export default function App(props) {
const [isLoadingComplete, setLoadingComplete] = useState(false)
if (!isLoadingComplete && !props.skipLoadingScreen) {
return (
<AppLoading
startAsync={loadResourcesAsync}
onError={handleLoadingError}
onFinish={() => handleFinishLoading(setLoadingComplete)}
/>
)
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle='default' />}
<ApolloProvider client={client}>
<AppNavigator />
</ApolloProvider>
</View>
)
}
}
async function loadResourcesAsync() {
await Promise.all([
Asset.loadAsync([
require('./assets/images/robot-dev.png'),
require('./assets/images/robot-prod.png')
]),
Font.loadAsync({
// This is the font that we are using for our tab bar
...Ionicons.font,
// We include SpaceMono because we use it in HomeScreen.js. Feel free to
// remove this if you are not using it in your app
'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf')
})
])
}
function handleLoadingError(error) {
// In this case, you might want to report the error to your error reporting
// service, for example Sentry
console.warn(error)
}
function handleFinishLoading(setLoadingComplete) {
setLoadingComplete(true)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
}
})
Now we’ll start working on the Home screen.
Home Screen
Before starting to work on HomeScreen.js
, let’s delete unnecessary files. Go to the components/
folder and delete StyledText.js
and the __tests__
folder.
Open up HomeScreen.js
and paste the following:
import React from 'react'
import { SafeAreaView, StyleSheet } from 'react-native'
import { Text } from 'react-native-elements'
import { ListJokes } from '../components/ListJokes'
class HomeScreen extends React.Component {
render() {
return (
<SafeAreaView>
<Text h1 h1Style={styles.h1}>
Dad Jokes
</Text>
<ListJokes />
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
h1: {
textAlign: 'center'
}
})
export default HomeScreen
Create a new file inside the components/
folder called ListJokes.js
and paste the following in it:
import { useMutation, useQuery } from '@apollo/react-hooks'
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { ListItem, Text } from 'react-native-elements'
import { Error } from './Error'
import { DELETE_JOKE_MUTATION } from './graphql/mutations/deleteJoke'
import { LIST_ALL_JOKES_QUERY } from './graphql/queries/jokes'
import { Loading } from './Loading'
const removeJoke = (id, deleteJoke) => {
deleteJoke({
variables: {
id
},
update: (cache, { data }) => {
const { jokes } = cache.readQuery({
query: LIST_ALL_JOKES_QUERY
})
cache.writeQuery({
query: LIST_ALL_JOKES_QUERY,
data: {
jokes: jokes.filter(joke => joke.id !== id)
}
})
}
})
}
export const ListJokes = () => {
const { loading, error, data } = useQuery(LIST_ALL_JOKES_QUERY)
const [deleteJoke] = useMutation(DELETE_JOKE_MUTATION)
if (loading) return <Loading />
if (error) return <Error />
const jokes = data.jokes
return (
<View style={styles.container}>
{!jokes.length ? (
<Text h4 h4Style={styles.center}>
No jokes in the database. Add one :)
</Text>
) : (
jokes.map((item, i) => (
<ListItem
key={i}
title={item.joke}
bottomDivider
rightIcon={{
name: 'delete',
onPress: () => removeJoke(item.id, deleteJoke)
}}
/>
))
)}
</View>
)
}
const styles = StyleSheet.create({
container: {
margin: 10
},
center: {
textAlign: 'center',
color: 'red'
}
})
Here, we use the useQuery
API from @apollo/react-hooks
. We pass in LIST_ALL_JOKES_QUERY
to it. And we get back 3 parameters, loading
, error
and data
.
We show <Loading />
component if loading
is true.
We show <Error />
component if error
is true.
Then if we don't have jokes we display a friendly message No jokes in the database. Add one :)
.
If we do have jokes in the database then we display the jokes.
We use ListItem
to render the jokes.
We specify a delete
icon in the rightIcon
parameter of ListItem
and onPress
it calls removeJoke
function.
We pass in deleteJoke
function to removeJoke
function. This deleteJoke
function we get when we call useMutation
with DELETE_JOKE_MUTATION
. When this function is called with an appropriate joke.id
, it deletes the joke from the database.
Later, we update the cache to filter it from our local cache. This optimistically updates the UI to remove deleted results from the UI without having to refresh the app.
Add Joke Screen
Open up AddJokeScreen.js
and paste the following:
import React from 'react'
import { SafeAreaView, StyleSheet } from 'react-native'
import { Text } from 'react-native-elements'
import { CreateJoke } from '../components/CreateJoke'
class HomeScreen extends React.Component {
render() {
return (
<SafeAreaView>
<Text h1 h1Style={styles.h1}>
Add Joke
</Text>
<CreateJoke />
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
h1: {
textAlign: 'center'
}
})
export default HomeScreen
Now lets create a new file called CreateJoke.js
in the components/
folder and paste the following in it:
import { useMutation } from '@apollo/react-hooks'
import React, { useState } from 'react'
import { Alert, StyleSheet, View } from 'react-native'
import { Button, Input } from 'react-native-elements'
import { Error } from './Error'
import { CREATE_JOKE_MUTATION } from './graphql/mutations/createJoke'
import { LIST_ALL_JOKES_QUERY } from './graphql/queries/jokes'
const saveJoke = (joke, changeJoke, createJoke) => {
if (joke.trim() === '') {
return
}
createJoke({
variables: { joke },
update: (cache, { data }) => {
const { jokes } = cache.readQuery({
query: LIST_ALL_JOKES_QUERY
})
cache.writeQuery({
query: LIST_ALL_JOKES_QUERY,
data: {
jokes: jokes.concat(data.createJoke)
}
})
}
})
Alert.alert('Joke added to the database')
changeJoke('')
}
export const CreateJoke = () => {
const [joke, changeJoke] = useState('')
const [createJoke, { error, data }] = useMutation(CREATE_JOKE_MUTATION)
if (error) {
return <Error />
}
return (
<View style={styles.wrapper}>
<Input
placeholder='Enter the joke'
value={joke}
onChangeText={changeJoke}
/>
<Button
type='outline'
title='Save Joke'
onPress={() => saveJoke(joke, changeJoke, createJoke)}
containerStyle={styles.button}
/>
</View>
)
}
const styles = StyleSheet.create({
wrapper: {
margin: 8
},
button: {
marginTop: 16,
padding: 4
}
})
It should look like:
Here, we simply add an Input
from react-native-elements
to enter the joke. Then we have Button
which when submitted calls saveQuote
with 3 parameters, namely, joke
, changeJoke
and createJoke
. We get createJoke
by calling in useMutation
with CREATE_JOKE_MUTATION
.
In saveQuote
function, we call in createJoke
with joke
variable. This creates a joke in the database. Then we optimistically update the UI to add the new joke to the list so we don't have to refresh the app to see the results.
Later, we throw an Alert
that the joke has been added and then we clear the Input
by calling in changeJoke
with empty string ''
.
Conclusion
In this tutorial, we built a Dad Jokes app with The Prisma Framework and React Native, totally inspired by icanhazdadjoke. You can find the complete code available here on Github.
The Prisma Framework (formerly, Prisma 2) allows us to write a query in our language of choice and then it maps everything out to a database so we don't have to worry about writing it in the database language. We can easily swap out any database by using it. Right now, it only supports SQLite, mySQL, and PostgreSQL but soon other databases will be supported when it comes out of beta.
Give it a shot and I'm sure you'll like the experience.