Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

MongoDB Developer
JavaScript
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
JavaScriptchevron-right

Getting Started With Bun and MongoDB

Joel Lord9 min read • Published Jul 19, 2024 • Updated Jul 19, 2024
Node.jsTypeScriptJavaScript
Facebook Icontwitter iconlinkedin icon
Rate this quickstart
star-empty
star-empty
star-empty
star-empty
star-empty
JavaScript has come a long way since its creation in the 1990s. It's now much more than a front-end programming language used to create fancy effects on a webpage. It has matured and is now an enterprise-grade back-end language with Node.js.
In the last few years, some alternatives for using JavaScript on the back end have emerged. In addition to Node.js, runtimes such as Deno aim to fix some of the issues engineers have with JavaScript.
This article will focus on Bun, a new generation runtime for running JavaScript or TypeScript on a server.
Bun is a new runtime designed for speed. It is optimized for large-scale web servers and includes modern tooling such as a built-in TypeScript interpreter, test runner, and package manager.
It is fully compatible with Node.js, and the packages you use with your other projects should work with Bun just as well.
In this quickstart, we'll introduce you to using Bun to write a simple CRUD API to read and write from a MongoDB collection. This simple guide uses the built-in packages from Bun to create a web server and the MongoDB Node.js driver to connect to the database.

Prerequisites

  • Bun: Install Bun by following the instructions on their website.
  • MongoDB: Use your own MongoDB instance or create a free-forever Atlas cluster
  • (Optional) Sample dataset: If you want to start with some data, you can import the sample dataset on your free Atlas cluster

TLDR

If you want to start with a MongoDB and Bun template, you can run the following commands.
1git clone https://github.com/mongodb-developer/bun-with-mongodb
2cd bun-with-mongodb
3bun i
4echo "MONGODB_URI=<your_atlas_connection_string>" > .env
5bun run index.ts

Set-up

Like in a typical Node.js project, you will need to start by initializing your application. This will create the necessary scaffolding for your application.
We will keep our project very simple for this tutorial and use the built-in Bun.serve web server. The only dependency required is the mongodb package from npm.
In a new folder, run the following commands to initialize your project and install the required dependencies.
1bun init
2bun add mongodb
If you look at the files in your folder, you should now see an index.ts file, along with the dependencies installed in the node_modules folder. If you are a Node.js developer, this should seem very familiar.

Application structure

Our application will be a simple API with five basic routes.
  • POST /movies: Will take a movie in the body and insert it in the collection
  • GET /movies: Retrieves the latest 10 movies that were added to the database
  • GET /movies/:id: Returns a single movie by id
  • PUT /movies/:id: Updates the movie specified in the path with the body
  • DELETE /movies/:id: Deletes the movie specified in the path
The routes will all be handled in the main index.ts file. The logic to connect to the database is located in the utils/db.ts file, and all the actions that are performed on the database can be found in the contollers/movies.ts file.

Starting the application

Let's start with a basic web server. Bun has multiple built-in packages, including Bun.serve. This package contains all the necessary components to build a basic web server.
Replace the content of the index.ts file with the following code:
1const server = Bun.serve({
2  async fetch(req) {
3    const url = new URL(req.url);
4    const method = req.method;
5    if (url.pathname === "/") return new Response("Welcome to the movie database");
6    return new Response("404!");
7  },
8});
9console.log(`Listening on http://localhost:${server.port} ...`);
Now, start the application with the following command.
1bun --watch run index.ts
Using --watch will automatically reload the server whenever a file changes. This is very convenient while you're in development mode.
By default, Bun will use port 3000 with Bun.serve. You can change that by setting your shell's PORT environment variable. Bun will automatically use the port defined in that environment variable.
To try it out, use Postman or a similar service, your web browser, or a CLI tool such as curl. For this article, I'll use curl.
Run the following command in a new terminal window to test your application.
1curl localhost:3000
You should see a message saying Welcome to the movie database.
Congrats! You have successfully built a web server with Bun --- time to connect to a MongoDB database.

Connecting to MongoDB

The connection to the MongoDB database will be handled by a file called utils/db.ts. Here, you will use the MongoDB driver (already installed with bun add) to create a MongoClient. You will then connect to the movies collection and export that collection for your controllers to use.
In a production environment, you'd also add some logic to ensure that the database connection is working properly, but for the purpose of this article, we'll stick to the basics.
Start with a new file called utils/db.ts.
1import { MongoClient } from "mongodb";
2let MONGODB_URI = process.env.MONGODB_URI;
3if (!MONGODB_URI) {
4  throw new Error("Please define the MONGODB_URI environment variable inside .env");
5}
6
7const client: MongoClient = await MongoClient.connect(MONGODB_URI);
8const moviesCollection = client.db("sample_mflix").collection("movies");
9
10export {
11  moviesCollection
12}
You will notice that the connection string to MongoDB (MONGODB_URI) is read directly from the environment variables. With Bun, no need to use a package to inject those variables into your application. It will automatically read from your .env file.
So, let's go ahead and create a .env file in your project's root folder.
1MONGODB_URI=<your_atlas_connection_string>
Replace the value of the environment variable with your connection string.
Great work! You can now connect to the database. The code isn't invoked anywhere, so there isn't much to test. Let's see how we can use this collection to perform CRUD operations.

Building a movie model

Bun uses TypeScript out of the box. You could also decide to use plain JavaScript, but since we're leveraging TS for this application, we'll need to create a movie model to tell our application what a movie looks like. For this quickstart, our movies will only have a title, some actors, and the year in which they were released.
Create a models/movies.ts file:
1import type { ObjectId } from "mongodb";
2
3// Create a custom type for our movies
4export interface Movie {
5  _id?: ObjectId,
6  title: string,
7  actors: string[],
8  year?: number,
9}
Note how we're also using the native MongoDB ObjectId type here for the _id field.
Because MongoDB is a database, your documents can contain many different properties, including arrays of strings, as we do in this example. We're keeping things simple here, but it could even be more complex objects, such as an array of actor objects. You can find out more about the document model on MongoDB University.
Now that you have your model, you can go ahead and create your movies controller.

Create a movies controller

You have a client connected to your database and a model that manages the format of your data. It's now time to do those CRUD operations on your database.
These operations are all handled in the movies controller. This controller communicates with the database and returns the operation's result to our server.
Create a new file called controllers/movies.ts
1// Import the Movies collection
2import { moviesCollection } from '../utils/db.ts';
3// Import the necessary types
4import type { ObjectId } from "mongodb";
5import type { Movie } from "../models/movies.ts";
6
7class MovieController {
8  /**
9   * CRUD operations for the movies collection
10   */
11
12  // Add a movie
13  public async addMovie(movie: Movie) {
14    return await moviesCollection.insertOne(movie);
15  }
16
17  // Fetch the latest ten movies
18  public async getMovies() {
19    return await moviesCollection.find({}).sort({_id: -1}).limit(10).toArray();
20  }
21
22  // Fetch one movie
23  public async getMovieById(_id: ObjectId) {
24    return await moviesCollection.findOne({_id});
25  }
26
27  // Update the movies
28  public async updateMovie(_id: ObjectId, movie: Movie) {
29    return await moviesCollection.updateOne({ _id }, { $set: movie });
30  }
31
32  // Delete a single movie
33  public async deleteMovie(movieId: ObjectId) {
34    return await moviesCollection.deleteOne({ _id: movieId });
35  }
36}
37
38export default MovieController;
This controller lists the most basic operations that can be performed on the database. If you want to learn more about those operations, I recommend the MongoDB and Node.js Tutorial - CRUD Operations on Developer Center. You can also look at our docs for the find, findOne, insertOne, updateOne, and deleteOne methods from our driver in our docs.
If you need more advanced operations, such as grouping or faceting, you should also look at the MongoDB Aggregation framework.
Now that you have all the code in place to perform your CRUD operations on your collection, it's time to return to the server and start with the request routing.

Server routing

It's now time to tie everything together in our index.ts file which is the code for the server itself. In this file, we'll import the necessary components, perform some routing, and call the appropriate methods from our controller.
First, import the necessary files at the top of the index.ts file.
1// Import the Movies functions
2import { ObjectId } from "mongodb";
3import MovieController from "./controllers/movies.ts";
4import type { Movie } from "./models/movies.ts";
This will import the movie controller we created earlier and the necessary types we'll use.
Next up, rewrite your Bun.serve logic to support various routes.
1const server = Bun.serve({
2  async fetch(req) {
3    const url = new URL(req.url);
4    const method = req.method;
5    if (url.pathname === "/") return new Response("Welcome to the movie database");
6
7    // Routes for the API
8    let moviesRoutes = new RegExp(/^\/movies\/?(.*)/)
9    const movies = new MovieController();
10
11    // POST /movies
12    if (url.pathname.match(moviesRoutes) && method === "POST") {
13      return new Response("Not implemented yet!");
14    }
15
16    // GET /movies and GET /movies/:id
17    if (url.pathname.match(moviesRoutes) && method === "GET") {
18      return new Response("Not implemented yet!");
19    }
20
21    // PUT /movies/:id
22    if (url.pathname.match(moviesRoutes) && method === "PUT") {
23      return new Response("Not implemented yet!");
24    }
25
26    // DELETE /movies/:id
27
28    if (url.pathname.match(moviesRoutes) && method === "DELETE") {
29      return new Response("Not implemented yet!");
30    }
31
32    return new Response("404!");
33  },
34});
We need to implement a full routing system because we don't use any framework here. In a production environment, you would most likely use a framework such as Hono or Express to help you with this.
In this case, we write a regular expression matching any route that starts with /movies. We also look at the method sent in the request. If the route doesn't match one of the handle routes or starts with /movies, we return the message 404!.
You can test these routes using curl.
1curl localhost:3000/movies
2curl localhost:3000/movies -X POST
3curl localhost:3000/movies -X PUT
4curl localhost:3000/movies -X DELETE
5curl localhost:3000/movies -X PATCH # 404!
Now's the time to connect your server to your database.

Connecting everything

We can finally put everything together.

Add a movie

To add a movie to the database, you must read the body of the request using req.json() and send this movie to your controller.
1    // POST /movies
2    if (url.pathname.match(moviesRoutes) && method === "POST") {
3      const movie: Movie = await req.json();
4      return Response.json(await movies.addMovie(movie));
5    }
Here, we're assuming that the request always matches a movie object, but in reality, you'd want to validate this. You would also likely want to handle any errors when inserting the data.
Once the code is in place, you can add a new movie to the collection.
1curl localhost:3000/movies -X POST --data '{"title": "New Movie"}'
You should see an acknowledgement that the operation worked and receive the new insertedId.

Read movies

Our GET /movies route is slightly more complex as it will handle both /movies to retrieve a list of 10 movies and /movies/:id to retrieve a single movie. To figure out which one to use, we'll look for the existence of a parameter after the /movies component.
If it's there, we'll take this string and convert it to an ObjectId before sending it to the movies controller. If the parameter isn't there, we use the getMovies method from the movies controller to return the latest 10.
1    // GET /movies and GET /movies/:id
2    if (url.pathname.match(moviesRoutes) && method === "GET") {
3      const routeParams = url.pathname.split("/");
4      if (routeParams[2]) {
5        const movieId: ObjectId = new ObjectId(routeParams[2]);
6        return Response.json(await movies.getMovieById(movieId));
7      } else {
8        return Response.json(await movies.getMovies());
9      }
10    }
You can now test those new routes. Note that you'll need to change the route of the second curl command to match an ObjectId from your existing movie collection.
1curl localhost:3000/movies
2curl localhost:3000/movies/668e855f76f86976e4045ae9

Update movies

We use the same trick to update a movie to find the id parameter and convert it to an ObjectId. We then pass the body of the request to the update method from our movies controller.
1    // PUT /movies/:id
2    if (url.pathname.match(moviesRoutes) && method === "PUT") {
3      const movieId: ObjectId = new ObjectId(url.pathname.split("/")[2]);
4      const movie: Movie = await req.json();
5      return Response.json(await movies.updateMovie(movieId, movie));
6    }

Delete movies

Finally, to delete a movie, we extract the movie id from the pathname and pass it to our movie controller's delete function.
1    // DELETE /movies/:id
2    if (url.pathname.match(moviesRoutes) && method === "DELETE") {
3      const movieId: ObjectId = new ObjectId(url.pathname.split("/")[2]);
4      return Response.json(await movies.deleteMovie(movieId));
5    }

What's next?

That's it! You have a fully functional server running on Bun that can connect to your MongoDB collection and perform basic CRUD operations. You can find all the code in our GitHub repository.
To deploy this code into production, you'd want to add more robustness to your code. You would also need to handle errors and return the appropriate error codes when a document is not found. But this is a good starting point for your first application.
If you have any questions, please register to our community forums, and use the form below to contact us!
Top Comments in Forums
There are no comments on this article yet.
Start the Conversation

Facebook Icontwitter iconlinkedin icon
Rate this quickstart
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Tutorial

UDF Announcement for MongoDB to BigQuery Dataflow Templates


Apr 02, 2024 | 4 min read
Tutorial

Multiple MongoDB Connections in a Single Application


Apr 02, 2024 | 10 min read
Article

How to Enable Local and Automatic Testing of Atlas Search-Based Features


Jun 12, 2024 | 8 min read
Tutorial

Visually Showing Atlas Search Highlights with JavaScript and HTML


Feb 03, 2023 | 7 min read
Table of Contents