Your first API with Bun, Express and Prisma
Tags: bunjs, express, prisma, api
Published: 2023-09-20 17:07:23


Have you seen this new, cool, and fast runtime for JavaScript, and are wondering how to begin developing web applications? Maybe this article will help. I love to see new ways to create applications that bring innovation to the JS ecosystem, and Bun brings a little more to it. Here, without further libraries, you can create your API, test it, bundle it, and even use its own SQLite integration, all of that while being a fast and easy-to-use runtime. It even has some frameworks already, but it's content for the future.
## Installation and Hello World

First of all, download and install bun using curl just like said in bun.sh
```bash
☁ ~ curl -fsSL 'https://bun.sh/install' | bash
######################################################################## 100,0%
bun was installed successfully to ~/.bun/bin/bun 
Run 'bun --help' to get started
```



Then, create a folder where you want your project to be, *cd* into it, and execute `bun init` , this will scaffold a new project, you'll have to choose the project's name and the entry point. By default the cli will use the name of your folder and start at index.ts.
```bash
☁  projects  mkdir bunApp  
☁  projects  cd bunApp  
☁  bunApp  bun init    
bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit

package name (bunapp): 
entry point (index.ts): 

Done! A package.json file was saved in the current directory.
 + index.ts
 + .gitignore
 + tsconfig.json (for editor auto-complete)
 + README.md

To get started, run:
  bun run index.ts
☁  bunApp  
```

After that, open your favorite ide(here im using vscode) and you'll see a very lean content, with some configuration files and a `index.ts` containing our Hello World!!
![IDE screen showing the file structure on the left and index file on the right](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3urue1vipf60mv8vabyn.png)
Almost all of those files are really common to every repository, but there's one called `bun.lockb`, its an auto-generated file similar to others `.lock` files and that's not of so much importance right now, but you can learn about it in the Bun documentation.
We already can run our `index.ts` file to start our little project,
```bash
☁  bunApp  bun index.ts
Hello via Bun!
☁  bunApp  
```
Before we continue to the next topic, there's one more thing to do. If you're familiar with Node, you've probably used Nodemon to monitor the project and reload when the code is changed. Bun simply uses the `--watch` tag to run in this mode, so you don't need an external module.
Let's add two scripts to our package.json, one to start the project and one for the developer mode, including the `--watch` tag.
```JSON
{
	"name": "bunapp",
	"module": "index.ts",
	"type": "module",
	"scripts": {
		"start": "bun run index.ts",
		"dev": "bun --watch run index.ts"
	},
	"devDependencies": {
		"bun-types": "latest"
	},
	"peerDependencies": {
		"typescript": "^5.0.0"
	},
}
```
## Routes

To initiate our server we just use `Bun.serve()`. It can receive some parameters, but for now we only need the `port` to access our application and the `fetch()` handler which we're using to deal with our requests. Write the code below and run our script `bun run dev` 
```ts
const server = Bun.serve({
	port: 8080,
	fetch(req) {
		return new Response("Bun!")
	}
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)
```

That's our first http request. As we didn't specified the method, it will return the response `Bun!` to any request made for our endpoint, which should be `localhost:8080`. We will deal with it in the next topic, for now lets just add some more code following the documentation example to compose our routes.
```ts
const server = Bun.serve({
	port: 8080,
	fetch(req) {
		const url = new URL(req.url)
		if(url.pathname === "/") return new Response("Home Page!")
		if(url.pathname === "/blog") return new Response("Blog!")
		return new Response("404!")
	}
})

console.log(`Listening on ${server.hostname}: ${server.port}...`)
```

It's taking our url from the request object and parsing to a URL object using Node's API. It happens because Bun aims for Node full compatibility, so most of libs and packages used on Node works on Bun out of the box.
## HTTP Requests

If you wish, use `console.log(req)` to view our request object, it looks like this:
```bash
Listening on localhost: 8080...
Request (0 KB) {
  method: "GET",
  url: "http://localhost:8080/",
  headers: Headers {
    "host": "localhost:8080",
    "connection": "keep-alive",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
    "accept-language": "en-US,en",
    "sec-fetch-mode": "navigate",
    "sec-fetch-dest": "document",
    "accept-encoding": "gzip, deflate, br",
    "if-none-match": "W/\"b-f4FzwVt2eK0ePdTZJcUnF/0T+Zw\"",
    "sec-ch-ua": "\"Brave\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Linux\"",
    "sec-gpc": "1",
    "sec-fetch-site": "none",
    "sec-fetch-user": "?1"
  }
}
```

Now here's the thing, we can use a LOT of conditionals to check the method and/or the endpoint. It becomes painfully polluted and doesn't look good to read
```ts
const server = Bun.serve({
	port: 8080,
	fetch(req) {
		const url = new URL(req.url)
		if(url.pathname === "/") return new Response("Home Page!")
		if(url.pathname === "/blog") {
		switch (req.method) {
			case "GET":
			// handle with GET
			case "POST":
			// handle with POST
			case "PUT":
			// handle with PUT
			case "DELETE":
			// handle with DELETE
			}
			// any other routes and methods...	
		}
		return new Response("404!")
	}
})
  
console.log(`Listening on ${server.hostname}: ${server.port}...`)
```

Remember that most Node packages runs on Bun as well? Let's use Express to ease our development process, we can view details in Bun's documentation. Let's start by stopping our application with `CTRL + C` and running `bun add express`
```bash
Listening on localhost: 8080...
^C
☁  bunApp  bun add express
bun add v1.0.2 (37edd5a6)

 installed express@4.18.2


 58 packages installed [1200.00ms]
☁  bunApp 
```
and rewrite our `index.ts` using a Express template with some routes
```ts
import express, { Request, Response } from "express";
  
const app = express();
const port = 8080;
app.use(express.json());
  
app.post("/blog", (req: Request, res: Response) => {
//create new blog post
});
  
app.get("/", (req: Request, res: Response) => {
res.send("Api running");
});
  
app.get("/blog", (req: Request, res: Response) => {
//get all posts
});
  
app.get("/blog/:post", (req: Request, res: Response) => {
//get a specific post
});
  
app.delete("/blog/:post", (req: Request, res: Response) => {
//delete a post
});

app.listen(port, () => {
console.log(`Listening on port ${port}...`);
});
```
## Adding a database


One last thing for our CRUD is to implement Database connections. Bun already has its own SQLite3 driver, but we're using Prisma since dealing with an ORM is easier. Let's follow the guide from Bun documentation and start by adding Prisma with `bun add prisma` and initializing it with `bunx prisma init --datasource-provider sqlite`. Then navigate to our new `schema.prisma` file and insert a new model.
```bash
☁  bunApp  bun add prisma                               
bun add v1.0.2 (37edd5a6)

 installed prisma@5.3.1 with binaries:
  - prisma


 2 packages installed [113.00ms]
☁  bunApp  bunx prisma init --datasource-provider sqlite

✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Run prisma db pull to turn your database schema into a Prisma schema.
3. Run prisma generate to generate the Prisma Client. You can then start querying your database.

More information in our documentation:
https://pris.ly/d/getting-started
    
☁  bunApp  
```
After that, run `bunx prisma generate` and then `bunx prisma migrate dev --name init`. Now we got what we need to our little API. Go back to our `index.ts`, import and initialize the Prisma client, then we're ready to finish our routes.
```ts
import { PrismaClient } from "@prisma/client";

/* Config database */
const prisma = new PrismaClient();
```
The final `index.ts` file should look like this in the end: 
```ts
import express, { Request, Response } from "express";
import { PrismaClient } from "@prisma/client";
  
/* Config database */
const prisma = new PrismaClient();
  
/* Config server */
const app = express();
const port = 8080;
app.use(express.json());
  
app.post("/blog", async (req: Request, res: Response) => {
	try {
		const { title, content } = req.body;
  
		await prisma.post.create({
			data: {
			title: title,
			content: content,
			},
		});
		res.status(201).json({ message: `Post created!` });
	} catch (error) {
		console.error(`Something went wrong while create a new post: `, error);
	}
});
  
app.get("/", (req: Request, res: Response) => {
	res.send("Api running");
});

app.get("/blog", async (req: Request, res: Response) => {
	try {
		const posts = await prisma.post.findMany();
		res.json(posts);
	} catch (error) {
		console.error(`Something went wrong while fetching all posts: `, error);
	}
});
  
app.get("/blog/:postId", async (req: Request, res: Response) => {
	try {
		const postId = parseInt(req.params.postId, 10);
  
		const post = await prisma.post.findUnique({
			where: {
				id: postId,
			},
		});
		if (!post) res.status(404).json({ message: "Post not found" });
		res.json(post);
	} catch (error) {
		console.error(`Something went wrong while fetching the post: `, error);
	}
});
  
app.delete("/blog/:postId", async (req: Request, res: Response) => {
	try {
		const postId = parseInt(req.params.postId, 10);
		await prisma.post.delete({
			where: {
			id: postId,
			},
		});
		res.send(`Post deleted!`);
	} catch (error) {
		return res.status(404).json({ message: "Post not found" });
	}
});

app.listen(port, () => {
	console.log(`Listening on port ${port}...`);
});
```
## Conclusion

Finally our API is done! From here on you can implement a bunch of other things such as: adding more models, creating a frontend and, of course, writing some tests(try do that next). You can see that Bun provides some quality of life while being compatible with most of Node packages, making our developer lives easier.