Go serverless with Vercel, SvelteKit, and MongoDB

What do you get when you combine Vercel, SvelteKit, and MongoDB Atlas? A powerful serverless-first development stack that scales massively.

Jigsaw puzzle pieces coming together.

The cloud continues to evolve toward higher orders of abstraction. Automated deployment and hosting platforms, front-end frameworks, and back-end databases are increasingly powerful and sophisticated, and integrating them is easier than ever. This article shows you how to integrate Vercel, SvelteKit, and MongoDB for full-stack serverless development. Each of these technologies leads in its own domain. By using them together, developers can achieve impressive capabilities with a modest amount of work.

Bite-sized bits: The sample application

For this demonstration, we’re going to build an application that creates a two-field entity to be stored in a database. From a user perspective, the application presents a form with two fields, one holding an apothegm and the other its author. (An apothegm is a bite-sized bit of wisdom.)

Our focus here is bringing together the elements of this powerful puzzle—Vercel, SvelteKit, and MongoDB—to take the sample application all the way from development to production.

To start, we will use SvelteKit to build a front-end view in Svelte. That view will submit requests to a back-end endpoint. Similar to Express.js, SvelteKit treats endpoints as abstractions of the request-response context.

The abstraction will make it easy to deploy to Vercel as a serverless context. The Vercel endpoint will simply stuff the received data into a collection in MongoDB Atlas, a cloud-native database as a service.

This setup will work fine for our development needs. Once we have the front end, we’ll create a repository in GitHub and check-in the project. Then, we can use Vercel to pull in our project and deploy it to a publicly exposed IP.

Full-stack development: SvelteKit

Let’s start with our development environment, SvelteKit. You can start a Svelte application from the SvelteKit command line as described in the framework's documentation. Once the application is running locally, you will be able to visit it and see the SvelteKit welcome screen.

To start, let's modify the application's main page to include a simple form. Edit /src/routes/index.svelte with the changes seen in Listing 1.

Listing 1. Modify the application main page (index.svelte)


<script context="module">
// export const prerender = true; Comment this out - this is now a dynamic page
</script>
<script>
  import Counter from '$lib/Counter.svelte';
  async function onSubmit(e) {
    const formData = new FormData(e.target);
    const data = {};
    for (let field of formData) {
      const [key, value] = field;
      data[key] = value;
    }
    console.log("formData: " + formData);
    const res = await fetch('/', {
      method: 'POST',
      body: JSON.stringify(data)
    })
    const json = await res.json()
    result = JSON.stringify(json)
  }
</script>
<svelte:head>
       <title>Home</title>
</svelte:head>
<section>
  <h1>
    <!-- remains the same ... -->       
  </h1>
  <form on:submit|preventDefault={onSubmit}>
    <label for="apothegm">Apothegm</label>
    <input type="text" name="apothegm" id="apothegm"/>
    <label for="author">Author</label>
    <input type="text" name="author" id="author"/>
    <button type="submit">Submit</button>
  </form>
       <h2>
               try editing <strong>src/routes/index.svelte</strong>
       <!-- rest is the same ...  -->  

Much of index.svelte remains the same. Note that I commented out the module export in the page head so that it's no longer pre-rendered. (One of SvelteKit’s superpowers is its ability to fully pre-render pages that don’t hit the back end. We have to disable that functionality because our page will hit the back end.)

The remainder of the changes are devoted to providing a form element with two fields. When the form is submitted, we'll marshal it into JSON and send it via a POST to the root endpoint (“/”) via fetch.

Handling the post function

The POST API call will be handled on the back end by src/routes/index.js, by whatever function lives under the name "post." Let’s turn to that now. Listing 2 shows the body of index.js.

Listing 2. index.js


import clientPromise from '../lib/mongo';

export async function post ({request}) {
 const dbConnection = await clientPromise;
 const db = dbConnection.db();
 const collection = db.collection('apothegm');
 let apothegm = await request.json();
 const dbApothegm = await collection.insertOne(apothegm);
 return { status: 200, body: { dbApothegm } }
}

The first thing we see in Listing 2 is an import to a helper library that we'll explore in a moment.  Next is the post function itself, which takes a request argument via destructuring from the SvelteKit framework. This request object holds everything we need to deal with an HTTP request. 

In our case, we open a database connection using the database helper, get a pointer to the "apothegm" collection, then grab the contents of the front-end body via the await request.json() method.

Finally, the method puts the request body into the database collection and sends back an "all good" status of 200.

The MongoDB connector

Now, let’s look at the /src/lib/mongo.js file, shown in Listing 3, which we use to hit the database. It is largely the canonical helper given by the MongoDB documentation, with a slight modification. Also note that, for the purpose of the demonstration, I chose to incorporate the database URL directly into the file. Don't do this in real life! It's a flagrant security hole. For a real world application, you would need to externalize the URL into an environment variable.

Listing 3. Connect to MongoDB (mongo.js)


import dotenv from 'dotenv';
dotenv.config();
import { MongoClient } from 'mongodb';
//const uri = process.env['MONGODB_URI'];
// **Don’t do this in real life**: 
const uri = "mongodb+srv://<username>:<password>@cluster0.foobar.mongodb.net/myFirstDatabase?retryWrites=true&w=majority";

const options = {
   useUnifiedTopology: true,
   useNewUrlParser: true,
}
let client
let clientPromise
if (!uri) {
   throw new Error('Please add your Mongo URI to .env.local')
}
if (process.env['NODE_ENV'] === 'development') {
   // In development mode, use a global variable
   // so that the value is preserved across module reloads
   // caused by HMR (Hot Module Replacement).
   if (!global._mongoClientPromise) {
       client = new MongoClient(uri, options)
       global._mongoClientPromise = client.connect()
   }
   clientPromise = global._mongoClientPromise
} else {
   // In production mode, it's best to
   // not use a global variable.
   client = new MongoClient(uri, options)
   clientPromise = client.connect()
}
// Export a module-scoped MongoClient promise.
// By doing this in a separate module,
// the client can be shared across functions.
export default clientPromise;

This helper is pretty straightforward. The biggest complexity is in handling the development versus production environments. Let’s move on to setting up the database.

MongoDB Atlas: The database as a service

MongoDB is a document-oriented database, one of the first and most prominent NoSQL datastores. Atlas is MongoDB’s managed cloud service, or database as a service (DBaaS). MongoDB Atlas lets you access a database hosted by MongoDB and use it via an API.

Note that for this next step, you’ll need to set up a free MongoDB Atlas account. Signing up is simple and quick. Once you have a new account, you’ll be taken to the dashboard, where you’ll create a new project by hitting the New Project button.

Next, you’ll be asked to name the new project, which I have called apothegm-foundry. You’ll also be offered the chance to add users and permissions, but you can ignore this offer because you were automatically added. Confirm the project by hitting Create Project.

Add a database

A project is a bucket for databases. Now, let’s add a database by clicking Build a Database. Here, you’ll be given a choice of tier. Using a free, shared database works for our purposes. When you are ready, hit Create

Next, you'll be offered a set of choices as to cloud providers and regions. You can accept the default for now, but it’s nice to see that we could select from Amazon Web Services (AWS), Google Cloud Platform (GCP), or Microsoft Azure. Click Create Cluster.

Next, you are invited to create a user for the database. You can create a username-password combination or a certificate-based user. We’ll take the username and password for ease. Pick a combination that you’ll remember and hit Create User. That’s the username and password you will put into mongo.js.

Now, scroll down to Where would you like to connect from. You could use your local IP address but for the purpose of this demo you can just enter 0.0.0.0/0. Again, we're keeping things simple here, but you would not enter a random IP address for a real-world application. You would need to enter the actual IP address or range of IP addresses.

From the main MongoDB Atlas console, you can always find your connection string by clicking on the database and hitting the Connect button.  Doing this gets you a pop-up where you can choose the Connect with Application option. This option provides a string of the form like so:


mongodb+srv://<username>:<password>@cluster0.foobar.mongodb.net/myFirstDatabase?retryWrites=true&w=majority

Add the username and password you've just selected, then return to the mongo.js file and add the string there. Now, when you use the form on the Svelte application and hit Submit, you should be able to go to the MongoDB Atlas console and see a Browse Collection button. 

You should see an entry reflecting what you entered in the form, similar to what I have in Listing 4.

Listing 4. Apothegm entry in MongoDB


1.	_id:6228f438e294d2c79754b64f
2.	apothegm:"Form and emptiness are one"
3.	author:"Unknown"

So, the development environment is working. Next up is deployment.

Deploy the application: GitHub and Vercel

Before we can deploy the application with Vercel, we need to create a source repository in GitHub. You’ll need a free GitHub account. Assuming you have that, follow the steps to create a new repository. Next, return to the command line and populate the repository with your application code. (Note that the SvelteKit starter has already added a .gitignore file.) Once the application source is checked into the main branch, you are ready to visit Vercel.

Vercel makes it easy to sign up for a free "Hobby" account. I used my GitHub account for SSO (single sign-on) access to Vercel. Once you have an account, follow the steps to connect your GitHub account and grant permission to Vercel.

You’ll also need to grant permission to Vercel inside GitHub for a specific repository or all repositories where you host code. Just open the dropdown on your account profile and hit Settings, then scroll down to the left-hand Integrations -> Applications option and click it.  Now, scroll down in the main page to the Repository Access section. There, you can either grant access to Vercel to the specific repository (as shown in Figure 1) or all of them.

Granting access to Vercel via GitHub. IDG

Figure 1. Granting access to Vercel via GitHub.

Next, go to Vercel and import the repository. Notice how Vercel detects the application as a SvelteKit application. It should seamlessly import and deploy the application.

Now, go to Vercel and you should see your application in the dashboard. Click on it and it’ll open the summary, which should look similar to the screen in Figure 2.

The application overview in Vercel. IDG

Figure 2. The application overview in Vercel.

You can click and open the running application at a URL like sveltekit-vercel-mongo.vercel.app.

If you enter a new apothegm and author, you should be able to reload the console using the MongoDB Atlas database collection view and see it reflected there. You production application is now up and working against a database.

Conclusion

There are three components to this stack, and they all work together fairly seamlessly. Vercel did a lot of lifting behind the scenes to make the production deployment happen. Among other things, notice that it can be configured to automatically deploy new pushes to the main branch.

Also notice that the build logs are available, as well as logs of the running application. The back-end part of the SvelteKit application was deployed as a serverless function, so its logs are available by clicking Deployments –> Functions.

Obviously, there's work to be done to harden this demo application into something you could actually use (as an example, you would want different databases for development and production). What is interesting is that you already have a powerful full-stack framework (SvelteKit), deployment pipeline (Vercel), and data store (MongoDB). And the whole things runs on infrastructure that can scale massively.

Copyright © 2022 IDG Communications, Inc.

How to choose a low-code development platform