Update (August 15th, 2023): Since this article has been published, the
react-prismapackage has been deprecated. You can query your database directly from a React Server Component using Prisma Client without the
At the time of writing, February 24th 2021, React Server Components are still being researched and far from being production-ready. The React core Team announced this feature to get initial feedback from the React community and in a spirit of transparency.
- React Server Components allow you to render components on the server and send them as data to your frontend. This data can be merged with the React client tree without losing state.
- Since Server Components live on the server we're going to see how to send queries to the database, skipping the API layer altogether. We'll do that using Prisma, an open-source ORM that provides an intuitive, type-safe API with clear workflows.
Here's the RFC and the full announcement talk:
This article summarizes the talk while making some changes to the official demo. Instead of sending raw SQL queries to the database, we'll be using Prisma, an open-source ORM. If you have watched the talk already, feel free to skip to the demo section of this article.
Using Prisma instead of plain SQL has several benefits:
- More intuitive querying (no SQL knowledge required)
- Better developer experience (e.g., through auto-completion)
- Safer database queries (e.g., prevents SQL injections)
- Easier to query relations
- Human-readable data model + generated (but customizable) SQL migration scripts
To learn more about Prisma and the different ways you can use it, check out the getting started guide.
- Good, fast, and cheap which two would you pick?
- Introducing Server Components
- Server Components demo
When you're building a product, you'll often face this dilemma, do you create:
- A product that is good and fast but is expensive
- A product that is good and cheap but is slow
- A product that is cheap and fast but isn't good
When building frontends, we face a similar dilemma. We have three goals:
- Create a consistent user experience. (good)
- A fast experience where data loads quickly. (fast)
- Low maintenance: adding or removing components shouldn't be complicated and should require little work. (cheap)
Which two do we pick? Let's take a look at three different examples.
Say we're building an app like Spotify, here's the mockup of what it should look like:
This page contains information about a single artist, such as their top tracks, discography, and details. If we were to build this UI using React, we'd break it down into multiple components. Here's how we'd write it using React while using static data, where each component contains its data.
To add data fetching logic to an API, we'd need to fetch all data at once and pass it down to the different components. This way, we can achieve a consistent user experience by rendering all components at once. So we would end up with something like this:
This approach is fast because we only need to make a single request to our API.
However, we find that the code is now harder to maintain. The reason being that the UI components are directly tightly coupled to the API response. So if we make a change in our UI, we need to update the API accordingly and vice-versa.
Otherwise, we may be passing unnecessary data that we're not using, or our UI won't render correctly.
So far, we have a good and fast user experience, but the code is harder to maintain.
Before adding the data fetching logic, we had an easy-to-maintain code where we could easily swap out components, so what happens if we try to make every component only fetch the data it needs?
If we add the data fetching logic inside each component, where each one fetches the data it needs, we'll end up with something like this.
This approach is not fast because our parent component's children only start fetching data after the parent makes a request, receives a response, and renders.
So we end up having a waterfall of network requests, where network requests start one after the other, instead of all at once:
What if we decide to decouple our components from the API by making separate requests and pass the data as props to our components? So in our Spotify app example, this is what our components will look like:
This pattern will result in inconsistent behavior because if all components start fetching data together, they don't necessarily finish simultaneously. That's because the data fetching process depends on the network connection, which can vary. So while now we have fast, easy-to-maintain code, we are sacrificing user experience.
So is it impossible to have all three? Not really.
Facebook faced this challenge and already came up with a solution using Relay and GraphQL fragments. Relay manages the fragments and only sends a single request, avoiding the waterfall of network requests issue.
Now while this may be a solution, not everyone can or wants to use GraphQL and relay. Perhaps you're working on a legacy codebase, or GraphQL is not the right tool for your use case.
So Facebook is now researching Server Components.
In this section, we'll take a closer look at React Server Components, how they work and what their benefits are compared to traditional, client-side React components.
When using React, all logic, data fetching, templating and routing are handled on the client.
However, with Server Components, components are rendered on the server. This allows our components to access all backend resources (i.e. database, filesystem, server, etc.). Also, since we now have access to the database, we can send queries directly from our components, skipping the API call step altogether.
After being rendered on the server, Server Components are sent to the browser in a JSON like format, which can the be merged with the client's component tree without losing state. (More details about the response format).
How is this different than server-side rendering (e.g. using Next.js)?
Server side rendered React is when we generate the HTML for a page when it is requested and send it to the client.
Server Components are complementary to server side rendering but behave differently, the hydration step is faster since it uses their prepared output.
When building web apps using React, we sometimes run into situations where we need to format data coming from an API. Say for example our API returns a
Date object, meaning the date will look like this:
1614637596145. A popular date formatting library is
date-fns. So what happens is we will include
With Server Components, we can use
date-fns to format the date object, render our component and then send it to the client. This way we don't need to include them in the client bundle. This is also why they're called "zero-bundle".
While this project works, there are still many missing pieces that are still being researched, and the API most likely will change. The following code walkthrough isn't a tutorial but a display of what's possible today.
Here's a link to the repository we'll reference in this article:
To run the app locally run the following commands
The app will be running at http://localhost:4000 and this is what you'll see:
When you clone the project you'll see the following directories:
/notes directory is where we save notes, in markdown format, when they're created on the frontend.
/prisma directory contains two files:
dev.dbfile, which is our SQLite database
schema.prismafile, the main configuration file for our Prisma setup that's used to define the database connection and the database schema.
The schema file is written in Prisma Schema Language (PSL). To get the best possible development experience, make sure you install our VSCode extension, which adds syntax highlighting, formatting, auto-completion, jump-to-definition, and linting for
We specified that we're using SQLite and our
dev.db file location in the
Next, we're specifying that we want to generate Prisma Client based on our data models in the
generator field. Prisma Client is an auto-generated and type-safe query builder; we're going to see how it simplifies working with databases.
Finally, in this schema, we have a
Note model with the following attributes:
Int, set as our primary key that auto-increments.
createdAt, of type
DateTimewith a default value of the creation time of an entry.
updatedAt, of type
- An optional
- An optional
All fields in a model are required by default. We specify optional fields by adding a question mark (?) next to the type.
/public directory contains static assets, a style sheet and an index.html file.
/scripts directory contains scripts for setting up webpack, seeding the database and initializing it.
/server directory contains a
This demo is a fullstack app with a REST API that has multiple endpoints for achieving CRUD operations. It's built using Express as the backend framework and Prisma to send queries to the database.
We're going to take a look at how the following functionalities are implemented:
- Creating a note.
- Getting all notes.
- Getting a single note by its id.
- Updating a note.
- Deleting a note.
When building REST APIs, Prisma Client can be used inside our route controllers to send databases queries. Since it is "only" responsible for sending queries to our database, it can be combined with any HTTP server library or web framework. Check out our examples repo to see how to use it with different technologies.
To create a note we created a
/notes endpoint that handles
POST requests. In the route controller we pass the
body and the
title of the note to the
create() function that's exposed by Prisma Client.
To get all notes, we created a
/notes route and when we receive a
GET request we will call and await the
findMany() function to return all records inside the
notes table in our database.
GET request to
/note/id will return a single note when we pass its
We get the note's
id from the request's parameters using
req.param.id and cast it to a number, since that's the type of the
id we defined in our Prisma schema.
We then use
findUnique which returns a single record by a unique identifier.
Finally, to update a note, we can send a
PUT requests to
/notes/:id and we access the note's id from the request parameters. We then pass it to the
update() function and pass the note's updates coming from the request's body.
To delete a note, we send a
DELETE request to
/notes/:id. We then pass the note's id from the request parameters to the
Note that all Prisma Client operations are promise-based, that's why we need to use async/await (or promises) when sending database queries using Prisma Client.
/src directory contains our React components, you'll notice
.server extensions. These extensions is how React distinguishes between a component that will be rendered on the client or on the server. All
.client files are just regular React components, so let's take a look at Server Components.
Now to access backend resources from React Server Components, we need to use special wrappers called React IO libraries. These wrappers are needed to tell React how to deduplicate and cache data requests.
These wrappers are not production-ready and most lilely will change.
So in the
db.server.js file, we're creating a new instance of Prisma Client. However notice that we're importing
react-prisma. This package allow us to use Prisma Client in a React Server Component.
NoteList.server.js component, we're importing
prisma and the
SidebarNote component, which is a regular React component that receives a note object as a prop.
We're filtering the list of notes by making a query to the database using Prisma.
We're retrieving all records inside the
notes table, where the
title of a note, contains the
searchText is passed as a prop to the component.
You'll notice that we don't need to
await prisma here, that's because React uses a different mechanism that retries rendering when the data is cached. So it's still asynchronous, but you don't need to use async/await.
You've now learned how to build an Express API using Prisma, consume it on the frontend and also use it in your React Server Components.
This new pattern is exciting because now we can have a good, fast user experience while having easy to maintain code. Because now, we have data fetching at the component-level.
Finally, React's virtual DOM now spans the entire application instead of just the client.
There are still many questions to be answered, and there are drawbacks, but it's exciting to see how the future of building Web apps using React might look like.
Don’t miss the next post!
Sign up for the Prisma Newsletter