Interested in speaking at MongoDB World 2022? Click here to become a speaker.
HomeLearnHow-toCreate a REST API with Cloudflare Worker, MongoDB Atlas, and Realm

Create a REST API with Cloudflare Worker, MongoDB Atlas, and Realm

Updated: Nov 22, 2021 |

Published: Nov 15, 2021

  • Atlas
  • Realm
  • JavaScript
  • ...

By Maxime Beugnet

 and Luke Edwards

Rate this article

#Introduction

Cloudflare Workers provides a serverless execution environment that allows you to create entirely new applications or augment existing ones without configuring or maintaining infrastructure.

MongoDB Atlas allows you to create, manage, and monitor MongoDB clusters in the cloud provider of your choice (AWS, GCP, or Azure) while MongoDB Realm Application can provide a layer of authentication and define access rules to the collections.

In this blog post, we will combine all these technologies together and create a REST API with a Cloudflare worker using the MongoDB Realm Web SDK and a MongoDB Atlas cluster to store the data.

#TL;DR!

The worker is in this GitHub repository. The README will get you up and running in no time, if you know what you are doing. Otherwise, I suggest you follow this step-by-step blog post. ;-)

1$ git clone git@github.com:mongodb-developer/cloudflare-worker-rest-api-realm-atlas.git

#Prerequisistes

We will create the Realm application together in the next section. This will provide you the Realm AppID and API key that we need.

To deploy our Cloudflare worker, we will need:

  • The Realm application ID (top left corner in your Realm app—see next section).
  • The Cloudflare account login/password.
  • The Cloudflare account ID (in Workers tab > Overview).

To test (or interact with) the REST API, we need:

  • The Realm authentication API key (more about that below, but it's in Authentication tab > API Keys).
  • The Cloudflare *.workers.dev subdomain (in Workers tab > Overview).

It was created during this step of your set-up:

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/create_subdomain_445263d59f.png

#Create and Configure the Realm Application

To begin with, head to your MongoDB Atlas main page where you can see your cluster and access the Realm tab at the top.

Create an empty Realm application (no template) as close as possible to your MongoDB Atlas cluster to avoid latency between your cluster and Realm app. My Realm app is "local" in Ireland (eu-west-1) in my case.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/access_realm_9e0e5322de.png

Now that our Realm app is created, we need to set up two things: authentication via API keys and collection rules. Before that, note that you can retrieve your Realm app ID in the top left corner of your new application.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/realm_appid_95ae074d2d.png

#Authentication Via API Keys

Head to Authentication > API Keys.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/realm_authentication1_61b8fdc932.png

Activate the provider and save the draft.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/realm_authentication2_14c746b282.png

We need to create an API key, but we can only do so if the provider is already deployed. Click on review and deploy.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/realm_review_deploy_6deae762a2.png

Now you can create an API key and save it somewhere! It will only be displayed once. If you lose it, discard this one and create a new one.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/realm_api_key_81bb588716.png

We only have a single user in our application as we only created a single API key. Note that this tutorial would work with any other authentication method if you update the authentication code accordingly in the worker.

#Collection Rules

By default, your Realm application cannot access any collection from your MongoDB Cluster. To define how users can interact with the data, you must define roles and permissions.

In our case, we want to create a basic REST API where each user can read and write their own data in a single collection todos in the cloudflare database.

Head to the Rules tab and let's create this new cloudflare.todos collection.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/realm_rules1_7840fef067.png

Each document will belong to a unique user defined by the owner field. This field will contain the user ID that you can see in the App Users tab.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/realm_rules2_d0647d30d9.png

You can now click one more time on Review Draft and Deploy. Our Realm application is now ready to use.

#Set Up and Deploy the Cloudflare Worker

The Cloudflare worker is available in GitHub repository. Let's clone the repository.

1$ git clone git@github.com:mongodb-developer/cloudflare-worker-rest-api-realm-atlas.git
2$ cd cloudflare-worker-rest-api-realm-atlas
3$ npm install

Now that we have the worker template, we just need to change the configuration to deploy it on your Cloudflare account.

Edit the file wrangler.toml:

  • Replace CLOUDFLARE_ACCOUNT_ID with your real Cloudflare account ID.
  • Replace MONGODB_REALM_APPID with your real MongoDB Realm app ID.

You can now deploy your worker to your Cloudflare account using Wrangler:

1$ npm i @cloudflare/wrangler -g
2$ wrangler login
3$ wrangler publish

Head to your Cloudflare account. You should now see your new worker in the Workers tab > Overview.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/cloudflare_worker_deployed_e2f606f4c5.png

#Check Out the REST API Code

Before we test the API, please take a moment to read the code of the REST API we just deployed, which is in the src/index.ts file:

1import * as Realm from 'realm-web';
2import * as utils from './utils';
3
4// The Worker's environment bindings. See `wrangler.toml` file.
5interface Bindings {
6 // MongoDB Realm Application ID
7 REALM_APPID: string;
8}
9
10// Define type alias; available via `realm-web`
11type Document = globalThis.Realm.Services.MongoDB.Document;
12
13// Declare the interface for a "todos" document
14interface Todo extends Document {
15 owner: string;
16 done: boolean;
17 todo: string;
18}
19
20let App: Realm.App;
21const ObjectId = Realm.BSON.ObjectID;
22
23// Define the Worker logic
24const worker: ExportedHandler<Bindings> = {
25 async fetch(req, env) {
26 const url = new URL(req.url);
27 App = App || new Realm.App(env.REALM_APPID);
28
29 const method = req.method;
30 const path = url.pathname.replace(/[/]$/, '');
31 const todoID = url.searchParams.get('id') || '';
32
33 if (path !== '/api/todos') {
34 return utils.toError(`Unknown "${path}" URL; try "/api/todos" instead.`, 404);
35 }
36
37 const token = req.headers.get('authorization');
38 if (!token) return utils.toError('Missing "authorization" header; try to add the header "authorization: REALM_API_KEY".', 401);
39
40 try {
41 const credentials = Realm.Credentials.apiKey(token);
42 // Attempt to authenticate
43 var user = await App.logIn(credentials);
44 var client = user.mongoClient('mongodb-atlas');
45 } catch (err) {
46 return utils.toError('Error with authentication.', 500);
47 }
48
49 // Grab a reference to the "cloudflare.todos" collection
50 const collection = client.db('cloudflare').collection<Todo>('todos');
51
52 try {
53 if (method === 'GET') {
54 if (todoID) {
55 // GET /api/todos?id=XXX
56 return utils.reply(
57 await collection.findOne({
58 _id: new ObjectId(todoID)
59 })
60 );
61 }
62
63 // GET /api/todos
64 return utils.reply(
65 await collection.find()
66 );
67 }
68
69 // POST /api/todos
70 if (method === 'POST') {
71 const {todo} = await req.json();
72 return utils.reply(
73 await collection.insertOne({
74 owner: user.id,
75 done: false,
76 todo: todo,
77 })
78 );
79 }
80
81 // PATCH /api/todos?id=XXX&done=true
82 if (method === 'PATCH') {
83 return utils.reply(
84 await collection.updateOne({
85 _id: new ObjectId(todoID)
86 }, {
87 $set: {
88 done: url.searchParams.get('done') === 'true'
89 }
90 })
91 );
92 }
93
94 // DELETE /api/todos?id=XXX
95 if (method === 'DELETE') {
96 return utils.reply(
97 await collection.deleteOne({
98 _id: new ObjectId(todoID)
99 })
100 );
101 }
102
103 // unknown method
104 return utils.toError('Method not allowed.', 405);
105 } catch (err) {
106 const msg = (err as Error).message || 'Error with query.';
107 return utils.toError(msg, 500);
108 }
109 }
110}
111
112// Export for discoverability
113export default worker;

#Test the REST API

Now that you are a bit more familiar with this REST API, let's test it!

Note that we decided to pass the values as parameters and the authorization API key as a header like this:

1authorization: API_KEY_GOES_HERE

You can use Postman or anything you want to test your REST API, but to make it easy, I made some bash script in the api_tests folder.

In order to make them work, we need to edit the file api_tests/variables.sh and provide them with:

  • The Cloudflare worker URL: Replace YOUR_SUBDOMAIN, so the final worker URL matches yours.
  • The MongoDB Realm app API key: Replace YOUR_REALM_AUTH_API_KEY with your Realm auth API key.

Finally, we can execute all the scripts like this, for example:

1$ cd api_tests
2 $ ./post.sh "Write a good README.md for Github"
3{
4 "insertedId": "618615d879c8ad6d1129977d"
5}
6 $ ./post.sh "Commit and push"
7{
8 "insertedId": "618615e479c8ad6d11299e12"
9}
10 $ ./findAll.sh
11[
12 {
13 "_id": "618615d879c8ad6d1129977d",
14 "owner": "6186154c79c8ad6d11294f60",
15 "done": false,
16 "todo": "Write a good README.md for Github"
17 },
18 {
19 "_id": "618615e479c8ad6d11299e12",
20 "owner": "6186154c79c8ad6d11294f60",
21 "done": false,
22 "todo": "Commit and push"
23 }
24]
25 $ ./findOne.sh 618615d879c8ad6d1129977d
26{
27 "_id": "618615d879c8ad6d1129977d",
28 "owner": "6186154c79c8ad6d11294f60",
29 "done": false,
30 "todo": "Write a good README.md for Github"
31}
32 $ ./patch.sh 618615d879c8ad6d1129977d true
33{
34 "matchedCount": 1,
35 "modifiedCount": 1
36}
37 $ ./findAll.sh
38[
39 {
40 "_id": "618615d879c8ad6d1129977d",
41 "owner": "6186154c79c8ad6d11294f60",
42 "done": true,
43 "todo": "Write a good README.md for Github"
44 },
45 {
46 "_id": "618615e479c8ad6d11299e12",
47 "owner": "6186154c79c8ad6d11294f60",
48 "done": false,
49 "todo": "Commit and push"
50 }
51]
52 $ ./deleteOne.sh 618615d879c8ad6d1129977d
53{
54 "deletedCount": 1
55}
56 $ ./findAll.sh
57[
58 {
59 "_id": "618615e479c8ad6d11299e12",
60 "owner": "6186154c79c8ad6d11294f60",
61 "done": false,
62 "todo": "Commit and push"
63 }
64]

As you can see, the REST API works like a charm!

#Wrap Up

Cloudflare offers a Workers KV product that can make for a quick combination with Workers, but it's still a simple key-value datastore and most applications will outgrow it. By contrast, MongoDB is a powerful, full-featured database that unlocks the ability to store, query, and index your data without compromising the security or scalability of your application.

As demonstrated in this blog post, it is possible to take full advantage of both technologies. As a result, we built a powerful and secure serverless REST API that will scale very well.

If you have questions, please head to our developer community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB. If your question is related to Cloudflare, I encourage you to join their active Discord community.

Rate this article
MongoDB logo
© 2021 MongoDB, Inc.

About

  • Careers
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2021 MongoDB, Inc.