June 18, 2019

Prisma 2 Preview: Type-safe Database Access & Declarative Migrations

nikolasburk
Nikolas Burk
@nikolasburk
Join the
discussion
24

We are excited to share the Prisma 2 Preview today. It features a type-safe database client (Photon) and a declarative migration system (Lift). Help us improve Prisma 2 by trying it out and sharing your feedback!

This is an early Preview of Prisma 2. Current limitations include missing features, limited performance and stability issues. We will address all these limitations before issuing a stable release later this year.

TLDR

Today we're launching a first Preview of Prisma 2. It consists of two major tools that simplify and modernize database workflows:

  • Photon: A type-safe and auto-generated databases client (think: ORM)
  • Lift: Declarative data modeling and database migrations

Photon and Lift can be used standalone or together in your application. Prisma 2 will be running in Preview for a few months. Please try it out and share your feedback!


Contents

Database workflows in 2019 are antiquated

In recent years, many areas of application development have been modernized to fit the new requirements brought by the era of digitization:

  • Frontend web applications are typically powered by declarative abstractions of the DOM (React, Vue, ...) instead of using static HTML and jQuery.
  • Backend developers benefit from modern languages and runtimes such as Node.js, Go or Elixir to make the right tradeoffs for their use case.
  • Compute used to be provisioned in private data centers. Today, most workloads can run in containers or serverless in public clouds.
  • Storage solutions are transitioning from being self-hosted to managed services like RDS or Azure Storage.

But what about the database workflows developers are dealing with every day? What's the next-generation tooling for accessing databases from within an application or for performing schema migrations?

For database access, developers can use traditional ORMs (e.g. Sequelize for Node.js or GORM for Go). While often beneficial to get a project off the ground, these aren't a good longterm fit as project complexity quickly outgrows the capabilities of traditional ORMs.

The tooling and best practices for schema migrations are even more fragmented and organizations tend to develop their own tools and processes for migrating database schemas.

Prisma 2: Next-generation database tooling

After helping large companies and individual developers solving their data access challenges for the last three years, we are proud to release a set of tools that help developers work with databases in modern development stacks.

Prisma 2 encompasses two standalone tools to tackle the problems of data access and migrations:

  • Photon: A type-safe database client for more efficient and safe database access
  • Lift: A modern and declarative migration system with custom workflows

Let's take a look at Photon and Lift in more detail.

Photon – A type-safe database client to replace traditional ORMs

Photon is a type-safe database client that's auto-generated based on the Prisma data model (which is a representation of your database schema). It provides a powerful and lightweight layer of mapping code you can use to talk to a database in your application.

It has a modern and ergonomic data access API that's tailored to the needs of application developers. You can explore the Photon API on the Photon site.

Lift – Declarative data modeling & migrations

Lift is based on Prisma's declarative data model definition which codifies your database schema(s). To migrate your database, you adjust the data model and "apply" the changes using the Lift CLI.

Every migration is represented via an explicit series of steps so you can keep a migration history throughout the lifetime of your project and easily can go back and forth between migrations. Migrations can also be exteneded with before/after hooks.

Better together: Seamless integration of Photon and Lift workflows

Both Photon and Lift can be used standalone in your applications, whether greenfield or brownfield. However, they're both seamlessly integrated through the Prisma CLI and work great together.

A shared foundation for both tools is the data model definition which has two responsibilities:

  • For Photon, it provides the models for the generated database client (CRUD API)
  • For Lift, it describes the schema of the underlying database(s)

The data model is at the heart of your Photon and Lift workflows. It serves as an intermediate abstraction between your database schema and the programmatic API you use to talk to your database.


Getting started with Prisma 2

Follow the steps below to get started with Photon and Lift in a simple TypeScript app. You can also check out the docs for more "Getting started"-options, such as using only Lift or only Photon or starting out with an existing database.

You can also check out this short video tutorial to get started.

1. Install the Prisma 2 CLI

You can install the Prisma 2 CLI using npm or Yarn:

npm install -g prisma2
Copy

2. Run the interactive prisma2 init flow & select boilerplate

Run the following command to get started:

prisma2 init hello-prisma2

Select the following in the interactive prompts:

  1. Select SQLite
  2. Check both options, Photon and Lift
  3. Select TypeScript
  4. Select From scratch

Once terminated, the init command created an initial project setup for you.

This creates a basic project structure for you:

  • project.prisma: The Prisma schema file that specifies your
    • data source (here: SQLite)
    • generator (here: Photon.js)
    • data model (here: Sample User and Post models)
  • tsconfig.json & package.json: Your TypeScript and Node.js configs
  • src/index.ts: Simple script that demonstrates some sample Photon API calls

Move into the hello-prisma2 directory and install the Node dependencies.

cd hello-prisma2
npm install

3. Migrate your database with Lift

Migrating your database with Lift follows a 2-step process:

  1. Save a new migration (migrations are represented as directories on the file system)
  2. Run the migration (to actually 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

4. Access your database with Photon

The script in src/script.ts contains some sample API calls, e.g.:

const allPosts = await photon.posts.findMany({
  where: { published: true },
})

const newPost = await photon.posts.create({
  data: {
    title: 'Join the Prisma Slack community',
    content: 'http://slack.prisma.io',
    published: false,
    author: {
      connect: { email: 'alice@prisma.io' },
    },
  },
})

const postsByUser = await photon.users
  .findOne({ where: {
    email: 'alice@prisma.io'
  }})
  .posts()
}

You can seed your database using the seed script from package.json:

npm run seed

You can execute the script with the following command:

npm run start

5. Build an app

With Photon being connected to your database, you can now start building your application. In the photonjs repository, you can find reference examples for the following use cases (for JavaScript and TypeScript):


Why Prisma is not like existing DB tools / ORMs

Developers typically use a mix of existing and custom/handwritten database tools for their every day database worfklows. Prisma unifies the main database workflows in a coherent ecosystem to make developers more productive.

Prisma uses a declarative data model

With Prisma, you define your models using a declarative and human-friendly data modeling syntax. The defined models get mapped to the underlying database(s) and at the same time provide the foundation for Photon's generated data access API.

Another major benefit of this approach is that the data model definition can be checked into version control so the entire team is always aware of the models the application is based on.

Photon is a type-safe and auto-generated database client

Traditional ORMs often don't cater to the complex requirements of large applications, but a data mapping layer is still needed. A typical solution is to handroll a custom data access layer for the application models.

Photon is auto-generated code that replaces the manual data access layer you'd write for your application anyways. Having it auto-generated ensures a consistent API, reduces human error and saves a lot of time otherwise spent writing CRUD boilerplate.

Photon provides a fully type-safe API (even for JavaScript). This API can be used as foundation to build more advanced ORM patterns (repository, active record, entities, ...).

Focus on developer experience & ergonomics

While most traditional ORMs try to simply abstract SQL into a programming language, Photon's data access API is designed with the developer in mind.

Especially when working with relations, Photon's API is a lot more developer-friendly compared to traditional ORMs. JOINs and atomic transactions are abstracted elegantly into nested API calls. Here are a few examples:

// Retrieve the posts of a user
const postsByUser: Post[] = await photon
  .users
  .findOne({ where: { email: "ada@prisma.io" }})
  .posts()

// Retrieve the categories of a post
const categoriesOfPost: Category[] = await photon
  .posts
  .findOne({ where: { id: 1 }})
  .categories()
Copy

Learn more about Photon's relations API in the docs.

Safe & resilient migrations for simple and complex use cases with Lift

Migrating database schemas can be an incredibly time-consuming and frustrating experience. Lift empowers developers with a simple model for migrations that's powerful enough for even the most complex use cases.

In the vast majority of cases, developers can simple adjust their declarative data model definition to represent the desired database structure, then save and run the migration:

prisma2 lift save # stores a new migration folder on the file system
prisma2 lift up   # applies the migration from the previous step

Whenever this workflow doesn't match your needs, you can extend it using "before/after"-hooks to run custom code before or after the migration is performed.

The migration folders (and the migrations history table in the database) let developers further do easy rollbacks of migration.

Lift is also designed to work seamlessly in CI/CD environments. In the future, Lift will enable immutable schema deploys (inspired by ZEIT's immutable deploys).

What's new in Prisma 2?

Prisma 2 not only splits up Prisma's main workflows into standalone tools, it also introduces fundamental improvements to each tool itself and provides a robust core for future development.

Improved datamodel syntax & project definition

In Prisma 1, there were two files that were required for every Prisma project:

  • prisma.yml: Root configuration file for the project
  • datamodel.prisma: Abstracts the database and provides foundation for generated Prisma client API

In Prisma 2, the configuration options and the data model have been merged into a single Prisma schema file, typically called schema.prisma.

Developers define a data model and specify how to connect to various data sources as well as target code generators (such as the photonjs-generator) in the schema file. A simple example of a new project definition could look as follows:

datasource pg {
  provider = "postgresql"
  url      = env("POSTGRES_URL")
}

generator js {
  provider = "photonjs"
}

model User {
  id         Int       @id
  email      String    @unique
  name       String
  posts      Post[]
}

model Post {
  id          Int       @id
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt
  draft       Boolean   @default(true)
  author      User
}

Prisma 2 also comes with a VS Code extension that provides auto-formatting and syntax highlighting (and more features like auto-completion, jump-to-definition and linting coming soon) for the data modeling syntax!

Learn more about the improved data modeling syntax in the docs.

Note for Prisma 1 users: The new data model syntax is heavily inspired by SDL. It has been optimized to describe database schemas, but in most use cases using it will feel very familiar to defining a Prisma 1 datamodel. Learn more in the spec.

Improved data access API & type-safe field selection in Photon

Photon provides a powerful data access API with some slight changes and improvements compared to the Prisma client API. Find the full API documentation here.

Unifying access to CRUD operations

The CRUD operations are unified across models and are accessible via a property on your Photon instance, e.g. for the model User you can access the operations to read and write data as follows:

const newUser = await photon.users.create({ data: {
  name: 'Alice'
}})

const allUsers = await photon.users.findMany()
const oneUser = await photon.users.findOne({
  where: { id: 1 }
})
Copy

Note that the name of the users property is generated using the pluralize package. You can find the API reference for Photon.js in the docs.

Type-safe field selection via select and include

A brand new feature in Photon's data access API is the ability to precisely specify the fields that should be returned from an API operation in a type-safe way. This can be done via either of two options that can be passed into any CRUD API call:

Note that include is not yet part of the Photon.js API but will be very soon!

Assume you your Photon API was generated from the data model above, here is an example for using select and include:

// Default selection
const oneUser = await photon.users.findOne({
  where: { id: 1 }
})
// oneUser = { id: 1, name: "Alice", email: "alice@prisma.io" }

// Select exclusively
const oneUser = await photon.users.findOne({
  where: { id: 1 },
  select: { email: true }
})
// oneUser = { email: "alice@prisma.io" }

// Include additionally
const oneUser = await photon.users.findOne({
  where: { id: 1 },
  include: { posts: true }
})
// oneUser = { id: 1, name: "Alice", email: "alice@prisma.io", posts: [ ... ] }

This code snippet only highlights select and include for findOne, but you can provide these options to any other CRUD operation: findMany, create, update and delete.

You can learn more about the field selection API of Photon.js in the docs.

Making the Prisma server optional

The Prisma server that was required as a database proxy in Prisma 1 is now optional.

This is due to a fundamental architecture change: the query and migration engines that previously ran inside the Prisma server can now run as plain binaries alongside your application on the same host.

The Prisma core is rewritten in Rust

Prisma 1 is implemented in Scala which means it needs to run in the JVM. To reduce the overhead of running Prisma, we decided to rewrite it in Rust.

Benefits of Rust include a significantly lower memory footprint, better performance and no more need to deploy, monitor and maintain an extra server to run Prisma 2.

Rust has shown to be the perfect language for Prisma, allowing us to write safe and extremely performant code.

Stabilize Prisma 2 & avoid future breaking changes

While Prisma 2 introduces a number of breaking changes, we strongly believe that those also come with fundamental improvements and are necessary to fulfill our vision of building a modern data layer for simple and complex applications.

We are investing a lot into the Preview period to ensure we're building upon a stable foundation in the future. During the Preview, there might still be breaking changes. After the GA release, we commit to providing a stable, non-breaking API.

Please help us by reporting any issues you encounter and asking questions that are not addressed in the docs.

We're further planning a number of blog posts that will dive deeper into specific parts of Prisma 2. This includes topics like the Rust rerwrite, how Lift works under the hood as well as articles that motivate many of our technical decisions and describe our core design principles.


Want a certain feature? Share your feedback!

We are super excited about the state of Prisma 2, but we don't want to stop here. We know that some decisions we made might be controversial, so if you have strong opinions please join the discussion to share your thoughts, ideas and feedback!

If you want to make an impact and help shaping what Prisma 2 will look like in the end, now is the right time to chime in! Leave your feedback in our #prisma2-preview channel on Slack or by opening an issue in the lift or photonjs repos.


See you at Prisma Day đź‘‹

The Prisma 2 Preview is not the only exciting thing happening this week. We are psyched for three days of conferences coming up:

We are especially looking forward to welcoming the Prisma community at Prisma Day for a day of inspiring talks and great conversations. See you all tomorrow! 🙌