Deno logoDeno
Persistent data npm modules in Deno.

Persist Data in Deno with npm using Prisma, Mongoose, Apollo, and more.

Andy Jiang


Persistent data is essential to building modern web apps. We need it to save user information, bill customers, and more. Now you can use your favorite data storage technologies—Prisma, Mongoose, MySQL, and more—with npm and Deno.

This blog post will show you how to quickly get started with these npm modules with Deno:

Check out our Manual for more How To guides on getting started with data persistence npm modules.

Safer data persistence with Deno

Supply chain attacks are a known security issue on npm. Node installs and runs npm modules with access to everything by default, making it easier for a single malicious dependency to quietly compromise millions of end users.

Data storage is at even greater risk to supply chain attacks, as production data is sensitive, necessary to run a business, and can be accessed through environment variables.

Deno's opt-in permission model ensures you know what your dependencies require access to. The permission system is granular enough for you to grant access to environment variables, the filesystem, and even FFI.

Prisma

Prisma, a modern ORM with best-in-class developer experience, has been one of our top requested modules. Here's a quick guide to getting started with Prisma and Deno.

This next section will show you how to quickly connect Prisma with Deno.

View source here or follow the video guide on our YouTube channel!

Setup the application

Let's create the folder deno-prisma and navigate there.

mkdir deno-prisma & cd deno-prisma

Then, let's run prisma init with Deno:

deno run --allow-read --allow-env --allow-write npm:prisma@^4.5 init

This will generate prisma/schema.prisma. Let's update it with the following:

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["deno"]
  output = "../generated/client"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Dinosaur {
  id          Int     @id @default(autoincrement())
  name        String  @unique
  description String
}

Prisma should also have generated a .env file with DATABASE_URL. Let's assign DATABASE_URL to a PostreSQL connection string. In this example, we'll use a free PostgreSQL database from Supabase.

After you have updated your .env file, let's create the database schema in Prisma:

deno run npm:prisma@^4.5 db push

After that's complete, we'll need to generate a Prisma client for Data Proxy:

deno run npm:prisma@^4.5 generate --data-proxy

Setup Prisma Data Platform

In order to use Prisma Data Platform, we'll have to create and connect a GitHub repo. So let's initialize the repository, create a new GitHub repo, add the remote origin, and push the repo.

After you've done that, sign up for a free Prisma Data Platform account.

Click New Project and select Import a Prisma Repository.

It'll ask for a PostgreSQL connection string, which you have in your .env. Paste it here. Then click Create Project.

You'll receive a new connection string that begins with prisma://. Let's grab that and assign it to DATABASE_URL in your .env file, replacing your PostgreSQL string from Supabase.

Using Prisma and PrismaClient

Now, you'll be able to import Prisma and PrismaClient into your Deno project:

import { Prisma, PrismaClient } from "../generated/client/deno/edge.ts";

From here, you can seed your database, query your data, and more.

Check out our more in-depth How To guide with Prisma or learn how to deploy Prisma on Deno Deploy.

Mongoose

Mongoose is a popular, schema-based library that models data for MongoDB. Here is a quick guide to getting started.

View source here or follow our video guide on YouTube.

Define a model

Let's define a model in Mongoose. In a new file Dinosaur.ts, let's add:

import { model, Schema } from "npm:mongoose@^6.7";

// Define schema.
const dinosaurSchema = new Schema({
  name: { type: String, unique: true },
  description: String,
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now },
});

// Validations
dinosaurSchema.path("name").required(true, "Dinosaur name cannot be blank.");
dinosaurSchema.path("description").required(
  true,
  "Dinosaur description cannot be blank.",
);

// Export model.
export default model("Dinosaur", dinosaurSchema);

Create a new Dinosaur

Let's create a new file, main.ts, that will:

  • connect to MongoDB
  • create a new Dinosaur
  • retrieve a dinosaur with name "Deno"
  • console.log the retrieved dinosaur
import mongoose from "npm:mongoose@^6.7";
import Dinosaur from "./model/Dinosaur.ts";

await mongoose.connect("mongodb://localhost:27017");

// Create a new Dinosaur.
const deno = new Dinosaur({
  name: "Deno",
  description: "The fastest dinosaur ever lived.",
});

// Insert deno.
await deno.save();

// Find Deno by name.
const denoFromMongoDb = await Dinosaur.findOne({ name: "Deno" });
console.log(
  `Finding Deno in MongoDB -- \n  ${denoFromMongoDb.name}: ${denoFromMongoDb.description}`,
);

Your output should be:

Finding Deno in MongoDB --
  Deno: The fastest dinosaur ever lived.

Success!

Check out our more in-depth How To guide with Mongoose.

Apollo

Apollo is a GraphQL server you can setup in minutes and use with your existing data source (or REST API). You can connect any GraphQL client to it to access the data and take advantage of typechecking and efficient fetching.

Let's get a simple Apollo server up and running that will allow us to query some local data. We’re only going to need three files for this:

  1. schema.ts to set up our data model
  2. resolvers.ts to set up how we’re going to populate the data fields in our schema
  3. Our main.ts where the server is going to launch

We’ll start by creating them:

touch schema.ts resolvers.ts main.ts

Let’s go through setting up each.

View source here.

Define our data with schema.ts

Our schema.ts file describes our data. In this case, our data is a list of dinosaurs. We want our users to be able to get the name and a short description of each dino. In GraphQL language, this means that Dinosaur is our type, and name and description are our fields. We can also define the data type for each field. In this case, both are strings.

This is also where we describe the queries we allow for our data, using the special Query type in GraphQL. We have two queries:

  • dinosaurs which gets a list of all dinosaurs
  • dinosaur which takes in the name of a dinosaur as an argument and returns information about that one type of dinosaur.

We’re going to export all this within our typeDefs type definitions, variable:

export const typeDefs = `
  type Dinosaur {
    name: String
    description: String
  }

  type Query {
    dinosaurs: [Dinosaur]
        dinosaur(name: String): Dinosaur
  }
`;

If we wanted to write data, this is also where we would describe the Mutation to do so. Mutations are how you write data with GraphQL. Because we are using a static dataset here, we won’t be writing anything.

Populate data in resolvers.ts

A resolver is responsible for populating the data for each query. Here we have our list of dinosaurs and all the resolver is going to do is either a) pass that entire list to the client if the user requests the dinosaurs query, or pass just one if the user requests the dinosaur query.

const dinosaurs = [
  {
    name: "Aardonyx",
    description: "An early stage in the evolution of sauropods.",
  },
  {
    name: "Abelisaurus",
    description: '"Abel\'s lizard" has been reconstructed from a single skull.',
  },
];

export const resolvers = {
  Query: {
    dinosaurs: () => dinosaurs,
    dinosaur: (_: any, args: any) => {
      return dinosaurs.find((dinosaur) => dinosaur.name === args.name);
    },
  },
};

With the later, we pass the arguments from the client into a function to match the name to a name in our dataset.

Set up main.ts

In our main.ts we’re going to import the ApolloServer as well as graphql and our typeDefs from the schema and our resolvers:

import { ApolloServer } from "npm:@apollo/server@^4.1";
import { startStandaloneServer } from "npm:@apollo/server@^4.1/standalone";
import { graphql } from "npm:graphql@^16.6";
import { typeDefs } from "./schema.ts";
import { resolvers } from "./resolvers.ts";

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 8000 },
});

console.log(`Server running on: ${url}`);

We pass our typeDefs and resolvers to ApolloServer to spool up a new server. Finally, startStandaloneServer is a helper function to get the server up and running quickly.

Running the server

All that is left to do now is run the server:

deno run --allow-net --allow-read --allow-env main.ts

You should see Server running on: 127.0.0.1:8000 in your terminal. If you go to that address you will see the Apollo sandbox where we can enter our dinosaurs query:

query {
  dinosaurs {
    name
    description
  }
}

This will return our dataset:

{
  "data": {
    "dinosaurs": [
      {
        "name": "Aardonyx",
        "description": "An early stage in the evolution of sauropods."
      },
      {
        "name": "Abelisaurus",
        "description": "\"Abel's lizard\" has been reconstructed from a single skull."
      }
    ]
  }
}

Or if we want just one dinosaur:

query {
  dinosaur(name:"Aardonyx") {
    name
    description
  }
}

Which returns:

{
  "data": {
    "dinosaur": {
      "name": "Aardonyx",
      "description": "An early stage in the evolution of sauropods."
    }
  }
}

Awesome!

Check out our more in-depth How To guide with Apollo.

What's next?

This blog post showed how to get started with Prisma, Mongoose, and Apollo. We also have How To guides for other data persistence npm modules such as PlanetScale, Redis, and MySQL2, and will continue to add more.

Stuck? Get help on our Discord.