# make project dir and initialize npmmkdirmyProjectcdmyProjectnpminit-y# install dev dependenciesnpmi-D@types/node@types/expressprismatypescripttsc-watchts-node# @types/_ - type definitions for _# prisma - orm toolkit# typescript - add TS# tsc-watch - nodemon but for transpiling .ts files --> .js# ts-node - lets you run TS files directly on Node.js (dont need tsc compiler)# install dependenciesnpmiexpressdotenvpg@prisma/client# express - for building api endpoints# dotenv - to read env variables like db url# pg - postgresdb# @prisma/client - query builder
Make Scripts
You need to define 4 scripts in package.json to:
"scripts": {"test":"echo \"Error: no test specified\" && exit 1",// 1. start the node server"start":"node dist/src/index.js",// 2. start local dev. server using tsc-watch (nodemon for TS)"dev":"tsc-watch --onSuccess \"node ./dist/src/index.js\"",// 3. run the seeding script (populate DB with starting data) (run using ts-node)"seed":"ts-node prisma/seed.ts",// 4. build script to make 'dist' folder where js is transpiled"build":"tsc"},
Directory Setup
Setup Prisma for your App
Since we installed the prisma as a devdep. above, we can now use its CLI (reference) which has important commands that we need.
# sets up prisma for your app; adds a prisma dir and env filenpxprismainit
Gitignore
If you haven't already, create a .gitignore file with the following content:
# .env has ur secrets like db url.env# transpiled js folder not really neededdistnode_modules
Define Project Directory
Create a folder in the project root named /src, with a single index.ts file where all the api endpoints will be defined.
Inside the /prisma dir, create a seed.ts file which is called by the above npm run seed script we defined to populate the db w/ intialize data.
main config file for your Prisma setup which houses:
data sources: specify details to connect to postgres db.
generators: specify what clients should be generated based on the data model (prisma client).
data model: specify your app's ACTUAL DATA SCHEMA and their relations.
reads the DB URL from .env automagically, so put ur connection URL in the .env - not here!
seed.ts
Uses the Prisma Client to populate the DB w/ initial data that is required for the app to start.
This ts-node file is ran using the npm run seed script defined above.
index.ts
We use Express paired with TS and the Prisma Client to define the actual API endpoints ie. GET/POST/PATCH/DELETE.
.env
This file was generated for you when you ran npx prisma init, and all you have to do is add in your DB URL here and it is read in the schema.prisma file.
.gitignore
files and folders specified here are not commited to the remote repo.
Should include .env, node_modules, and dist.
package.json
You defined your scripts here.
Create Data Model in schema.prisma
First, install the prisma vs code ext. for syntax highlighting and autofills. Now let's define our actual data models in the schema.prisma file.
Model = Table.
An example model is provided below:
model Post { id Int @id @default(autoincrement()) comments Comment[] // a post can have many commments}// model names are PascalCase and singular (User; not: user, users, or Users)model Comment {// always include these 3, they're really common and often required id Int @id @default(autoincrement()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt// <name> <type> <optional attributes> title String content String// A comment can belong to 1 post Post Post? @relation(fields: [postId], references: [id]) postId Int?}
Note:
Instead of manually defining a relation, use the autocomplete feature of the prisma vs code ext.; ie. for a Book-Author model where Book has 1 Author and Author has many Book
model Author { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt name String}model Book { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt Title String Genre String// 1) Each Book has 1 Author, so type the below & hit SAVE; let prisma ext. do it! author Author}// -------------THIS TURNS INTO:---------------------model Author { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt name String Book Book[] // new: List of Books with authorId = this.id }model Book { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt Title String Genre String author Author @relation(fields: [authorId], references: [id], onDelete: Cascade) // new authorId Int // new}
Now Synchronize Schemas
Now synchronize your Prisma schema (from schema.prisma) with your database schema.
# run the following to sync. schemas$npxprismadbpush# open the Prisma studio, a visual editor for the data in your database$npxprismastudio
Note: db push works well for prototyping; otherwise, see prisma migrations.
Create Seeder Function
seed.ts Uues the Prisma Client to populate the DB w/ initial data that is required for the app to start. This ts-node file is ran using the npm run seed script previously defined.
import { PrismaClient } from"@prisma/client";// this PrismaClient object is used to build queriesconstprisma=newPrismaClient();asyncfunctionrun() {constauthors= [ {Name:"Rick Riordan", isBestSeller:true}, {Name:"Joanne Rowling", isBestSeller:true}, {Name:"Jeff Kinney", isBestSeller:true}, {Name:"John Tolkien", isBestSeller:true}, {Name:"George Martin", isBestSeller:true} ]// this task was automated,for(let index:number=0; index<authors.length; index++) {const [firstName,lastName] = authors[index].Name.split(" ");// in the Author table, upsert (update if exists, create otherwise)consttempAuthor=awaitprisma.author.upsert({ where: { id: index +1 },// where it matches this criteria update: {},// update these fields (leave empty if you want to create) create: { // create a record with the following fields (see schema) id: index +1, firstName, lastName, publishedBestSeller: authors[index].isBestSeller, } })console.log(tempAuthor); }/* You can also enter single records like a primate. const riordan = await prisma.author.upsert({ where: { id: 1 }, update: {}, create: { id: 1, firstName: "Rick", lastName: "Riordan", publishedBestSeller: true } }) */}// When you run 'npm run seed', ts-node will run this file, and it will execute this run() function that's called below.
run().catch((err) => {console.log(err);process.exit(); }).finally(async () => {awaitprisma.$disconnect(); })
Define API Endpoints
You can use the below express API app example as a guide; pay attention to how the queries are built:
import"dotenv/config"import express from"express"import { Prisma, PrismaClient } from"@prisma/client";constapp=express();constprisma=newPrismaClient();constPORT=3001;// Middlewareapp.use(express.json());app.use(express.urlencoded({ extended:true }))// GET ALL BOOKS - $findManyapp.get('/books',async (req, res, next) => {try {constbooks=awaitprisma.book.findMany({ where: { published:true }, orderBy: { createdAt:"desc" } })res.json({ books }); } catch (error:any) {next(error.message); }});// POST (CREATE) a BOOK - $createapp.post('/books',async (req, res, next) => {try {constbook=awaitprisma.book.create({ data: { authorId:1,...req.body } })res.json({ book }) } catch (error:any) {next(error.message); }});// GET a BOOK by ID - $findUniqueapp.get('/books/:id',async (req, res, next) => {try {constbook=awaitprisma.book.findUnique({ where: {id:Number(req.params.id)} })res.json({ book }); } catch (error:any) {next(error.message) }});// UPDATE a BOOK by ID - $updateapp.patch('/books/:id',async (req, res, next) => {try {constbook=awaitprisma.book.update({ where: { id:Number(req.params.id) }, data:req.body, })res.json({ book }); } catch (error:any) {next(error.message); }});// DELETE a BOOK by ID - $deleteapp.delete('/books/:id',async (req, res, next) => {try {constbook=awaitprisma.book.delete({ where: { id:Number(req.params.id), } })res.json({ book }); } catch (error:any) {next(error.message); }});// GET a AUTHOR's BOOKS - $findUniqueapp.get('/author/:id/books',async (req, res, next) => {try {constauthorsBooks=awaitprisma.author.findUnique({ where: { id:Number(req.params.id), }, include: { Book: { where: { published:true } } } })res.json({ authorsBooks }); } catch (error:any) {next(error.message); }});app.listen(PORT, () => {console.log(`Listening on port ${PORT}`);})
Usually, after any updates in the schema, if you want to flush the DB and reset Prisma to resync the DB: