Build a REST API with Prisma, Node JS and Typescript.

joshtom

Joshua Olajide

Posted on March 11, 2023

Build a REST API with Prisma, Node JS and Typescript.

Introduction

The process of learning a new technology can often feel tedious, especially in the initial stages when you have yet to see it in action.

This article will guide you through the process of integrating these amazing technologies and demonstrate how to build a small application using them.

During the course of this article, we will develop a blog application that showcases how CRUD operations work in practice, with fundamental functions including:

  • Creating new posts
  • Retrieving all posts or a specific post
  • Modifying post content
  • And ultimately, removing a post
  • An additional feature we will implement is the inclusion of a like count for each post.

CRUD means CREATE, READ, UPDATE and DELETE

If you are interested in checking out the code, Check out the github repository here

Prerequisite

Before you proceed, it's important to be acquainted with the following set of tools:

  • Node.js and Express.js
  • TypeScript
  • Prisma

If you require a brief overview of Prisma, my blog post here can be a helpful resource.

Let's Get Started πŸš€

Open up your terminal then create and navigate to your folder directory using the below command

mkdir prisma-typescript-blog && cd prisma-typescript-blog
Enter fullscreen mode Exit fullscreen mode

Next, Initialize the project using yarn

yarn init -y
Enter fullscreen mode Exit fullscreen mode

Then, Install the Dependencies

yarn add -D @types/express @types/node prisma ts-node-dev typescript 
Enter fullscreen mode Exit fullscreen mode
yarn add express @prisma/client
Enter fullscreen mode Exit fullscreen mode

Finally, set up Prisma with the init command of the Prisma CLI:

npx prisma init --datasource-provider sqlite 
Enter fullscreen mode Exit fullscreen mode

This creates a new prisma directory with your Prisma schema file and configures Sqlite as your database.

Set up Prisma Schema

prisma/prisma.schema

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

generator client {
  provider = "prisma-client-js"
}

model Post {
  id         Int      @id @default(autoincrement())
  title      String
  content    String
  likesCount Int      @default(0)
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  // Relations
  comments Comment[]
}

model Comment {
  id        Int      @id @default(autoincrement())
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  post   Post @relation(fields: [postId], references: [id])
  postId Int
}

Enter fullscreen mode Exit fullscreen mode

In the above schema we define our Blog Models which includes Post and Comment

post   Post @relation(fields: [postId], references: [id])
Enter fullscreen mode Exit fullscreen mode

This line of code means that there's a one-to-many relationship between the Post and Comment models. One post can have many comments, and each comment is associated with only one post. The postId field in the Comment model is used to reference the id field in the Post model.

Once you are done with this, Update your package.json to look like this:

{
  "name": "prisma-typescript-blog",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "ts-node-dev --respawn --transpile-only --exit-child src/server.ts",
    "db:migrate": "npx prisma migrate dev --name user-entity --create-only && npx prisma generate",
    "db:push": "npx prisma db push"
  },
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": [
      "es6",
      "dom"
    ]
  },
  "devDependencies": {
    "@types/express": "^4.17.17",
    "prisma": "^4.11.0",
    "ts-node-dev": "^2.0.0",
    "typescript": "^4.9.5"
  },
  "dependencies": {
    "@prisma/client": "^4.11.0",
    "dotenv": "^16.0.3",
    "express": "^4.18.2"
  }
}

Enter fullscreen mode Exit fullscreen mode
  • start Starts the Node Js Server
  • db:migrate - Generate the migration file and Prisma Client
  • db:push - Pushes the Prisma schema to the database

Migration

At this point, you have a Prisma model but no database yet. Open your terminal and run the following command to create the SQLite database and the β€œposts” table represented by the model.

yarn db:migrate
Enter fullscreen mode Exit fullscreen mode

After running the command, the Prisma CLI will analyze your schema and generate a migration file in the prisma/migrations directory.

Push the Prisma schema to the database:

yarn db:push
Enter fullscreen mode Exit fullscreen mode

After synching the SQLite database with the Prisma schema, run npx prisma studio to open the Prisma GUI tool in the browser. Prisma studio allows you to view and mutate the data stored in the database.

By now, Your browser should automatically open a new tab and you will see something like this:

Prisma Studio Example

Project Structure 🚧

Edit your project structure to look like below

`prisma` -|
          `schema.prisma`
`src` -|
     `controllers`
          -| 
           `post.controller.ts`
     `routes`
          -|
           `post.route.ts`
     `server.ts`
.env
`tsconfig.json`
Enter fullscreen mode Exit fullscreen mode

.env

DATABASE_URL="file:./dev.db"
Enter fullscreen mode Exit fullscreen mode

Replace whatever is in your .env file with the above snippet

Let's move on to the fun part πŸ’ƒ

server.ts

import express, { Request, Response } from "express";
import { PrismaClient } from "@prisma/client";
import PostRouter from "./routes/blog.route";

export const prisma = new PrismaClient();

const app = express();
const port = 8080;

async function main() {
  app.use(express.json());

  // Register API routes
  app.use("/api/v1/post", PostRouter);

  // Catch unregistered routes
  app.all("*", (req: Request, res: Response) => {
    res.status(404).json({ error: `Route ${req.originalUrl} not found` });
  });

  app.listen(port, () => {
    console.log(`Server is listening on port ${port}`);
  });
}

main()
  .then(async () => {
    await prisma.$connect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });
Enter fullscreen mode Exit fullscreen mode

Quite a lot is going on here, so let me explain

  • prisma = new PrismaClient() - Creates a new instance of the Prisma client
  • The express.json() Registers middleware to parse incoming request bodies as JSON.
  • app.use("/api/v1/post", PostRouter); Registers API routes using the PostRouter module.
  • app.all("*",... Defines a catch-all route to handle unregistered routes and return a 404 error response.

When the main() function is called, it starts the Express server and connects to the database using the Prisma client. If there are any errors, it logs the error to the console and disconnects from the database.

The code serves as a starting point for building an API server with Node.js, Express.js, and Prisma ORM. It demonstrates how to handle API routes, connect to a database, and start a server that listens for incoming requests.

Routes

routes/post.route.ts

import express from "express";
import PostController from "../controllers/post.controller";

const router = express.Router();

router.post("/create", PostController.createBlogPost);
router.post("/createPostAndComments", PostController.createPostAndComments);
router.get("/getall", PostController.getBlogPosts);
router.get("/get/:id", PostController.getBlogPost);
router.put("/update/:id", PostController.updateBlogPost);
router.delete("/delete/:id", PostController.deleteBlogPost);
router.delete("/deleteall", PostController.deleteAllBlogPosts);
router.post("/like", PostController.likeBlogPost);

export default router;

Enter fullscreen mode Exit fullscreen mode

These routes are pretty straight forward except for the /createPostAndComments route. Each route handles a specific HTTP method (POST, GET, PUT, or DELETE). The routes are mapped to methods defined in the PostController controller, which handles the logic for each of these routes.

The /:id portion of these routes is a parameter that represents a dynamic value that can be passed in the URL. The id parameter is a placeholder for a specific value, such as a unique identifier for a blog post.

The createPostAndComments will help us demonstrate a one to many relationship as it was defined in the Schema earlier.

In the Post Prisma model, there is a one-to-many relationship between Post and Comment, where one Post can have many Comments, but each Comment can only belong to one Post. This relationship is modeled in the Prisma schema using the comments field in the Post model:

model Post {
  id         Int      @id @default(autoincrement())
  title      String
  content    String
  likesCount Int      @default(0)
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  // Relations
  comments Comment[]
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the comments field is an array of Comment objects, which represents the one-to-many relationship.

Controllers

controllers/post.controller.ts

import { Request, Response } from "express";
import { prisma } from "../server";

const createBlogPost = async (req: Request, res: Response) => {
  try {
    const { title, content } = req.body;
    const newBlogPost = await prisma.post.create({
      data: {
        title,
        content,
      },
    });
    res.status(200).json(newBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const createPostAndComments = async (req: Request, res: Response) => {
  try {
    const { title, content, comments } = req.body;
    const newBlogPost = await prisma.post.create({
      data: {
        title,
        content,
        comments: {
          create: comments,
        },
      },
      include: {
        comments: true, // Include the comments in the response
      }
    });
    res.status(200).json(newBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const getBlogPosts = async (req: Request, res: Response) => {
  try {
    const blogPosts = await prisma.post.findMany();
    res.status(200).json(blogPosts);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const getBlogPost = async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const blogPost = await prisma.post.findUnique({
      where: {
        id: Number(id),
      },
    });
    res.status(200).json(blogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const updateBlogPost = async (req: Request, res: Response) => {
  try {
    const { id, title, content } = req.body;
    const updatedBlogPost = await prisma.post.update({
      where: {
        id: Number(id),
      },
      data: {
        title,
        content,
      },
    });
    res.status(200).json(updatedBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const deleteBlogPost = async (req: Request, res: Response) => {
  try {
    const { id } = req.body;
    const deletedBlogPost = await prisma.post.delete({
      where: {
        id: Number(id),
      },
    });
    res.status(200).json(deletedBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const deleteAllBlogPosts = async (req: Request, res: Response) => {
  try {
    const deletedBlogPosts = await prisma.post.deleteMany();
    res.status(200).json(deletedBlogPosts);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const likeBlogPost = async (req: Request, res: Response) => {
  try {
    const { id } = req.body;
    const likedBlogPost = await prisma.post.update({
      where: {
        id: Number(id),
      },
      data: {
        likesCount: {
          increment: 1,
        },
      },
    });
    res.status(200).json(likedBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

export default {
  createBlogPost,
  createPostAndComments,
  getBlogPosts,
  getBlogPost,
  updateBlogPost,
  deleteBlogPost,
  deleteAllBlogPosts,
  likeBlogPost,
};


Enter fullscreen mode Exit fullscreen mode

This controller is quite bulky, so I will go ahead and explain each method one after the other.

Our methods are listed as follows

  • createBlogPost
  • createPostAndComments
  • getBlogPosts
  • getBlogPost
  • updateBlogPost
  • deleteBlogPost
  • deleteAllBlogPosts
  • likeBlogPost

createBlogPost
This is a pretty simple method that accepts the title and content. It first destructures the title and content values from the req.body object, which is the request payload.

Also, it uses the create() method to create a new blog post in the database. This method returns a Promise, which is why the await keyword is used to wait for the operation to complete before continuing execution.

When a request is made to the http://localhost:8080/api/v1/post/create endpoint with the credentials included in the request body, the createBlogPost handler will be evoked to create a new blog post.

Blog post postman demo

Postman is my preferred tool for testing all the endpoints, but feel free to use whichever testing tool you prefer.

createPostAndComments

The createPostAndComments method expects the request body to include a title, content, and an array of comments. The method then creates the new Post object using the create() method provided by Prisma, and includes the comments in the data object as well.

The Comment object will include the content field, as the postId field will be automatically set by Prisma when the Comment is associated with the new Post.

Finally, the include parameter is used to include the comments for the newly created Post in the response.

Here is how the payload and response looks like

Example Body

Example Response

updateBlogPost
This method first extracts the id, title, and content properties from the request body using object destructuring.

It then uses Prisma to update the blog post with the specified id. The update() method is called on the post model, passing in an object that specifies the where and data clauses. The where clause specifies the unique identifier for the blog post to be updated, while the data clause specifies the new values for the title and content properties.

likeBlogPost
This method first extracts the id property from the request body. This id represents the unique identifier of the blog post to be liked.

It then uses Prisma prisma.post.update() to update the specified blog post. The update() method is called on the post model, passing in an object that specifies the where and data clauses. The where clause specifies the unique identifier for the blog post to be updated, while the data clause increments the likesCount property by 1 using the increment keyword

Here is it in action

When a request is made to the http://localhost:8080/api/v1/post/like endpoint passing id as body, It then invokes the createBlogPost handler and likesCount will be incremented

Like Image

All other methods are quite similar to what has been covered so I won't be going over them

After all these steps, Be sure your server is still running if not run yarn start to get it up and running.

In Conclusion

This article demonstrates how to apply your knowledge of this amazing tools by creating a blog API using Node, Typescript, and Prisma. Additionally, the tutorial covers the process of connecting the API with a SQLite database.

Congratulations on reaching the end of this article! If you found it helpful, please consider giving it a thumbs up and leaving your comments below.

See you in the next πŸ˜‰πŸš€

πŸ’– πŸ’ͺ πŸ™… 🚩
joshtom
Joshua Olajide

Posted on March 11, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related