Persist Data in Deno with npm using Prisma, Mongoose, Apollo, and more.
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.
Prisma
and PrismaClient
Using 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);
Dinosaur
Create a new 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:
schema.ts
to set up our data modelresolvers.ts
to set up how we’re going to populate the data fields in our schema- 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.
schema.ts
Define our data with 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 dinosaursdinosaur
which takes in thename
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.
resolvers.ts
Populate data in 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.
main.ts
Set up 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.