Making an External Database Using Replit

Hello Developers!

Have you ever wanted to back-up datastore data? Have more complex data structures? Or maybe you want to be able to access your data from other platforms (eg. Discord), to do this you’ll need to use an external database.

When I wanted to do this myself, I didn’t even know where to begin and couldn’t find a helpful tutorial on here so I want to enlighten everyone who wants to create their own custom database!

Prerequisites:

Setting Up Our Database

The first thing we need to do is create the database on MongoDB. MongoDB has free and paid options, making it easy to scale your project as your game grows larger.

First things first, we need to make an account. Navigate to the top bar and look to the far right. Click Try Free.

Scroll down to the bottom, and sign up with your google account to make things fast and easy. Accept the terms of service and privacy policy. You’ll have to answer a quick quiz, fill it out based on the use of of your Roblox game. Make sure you set your preferred language to JavaScript as that’s what we’ll be using in this tutorial.

Once you click finish, you’ll be provided several options on how to pay for your database. In this case I will be choosing the free plan, but you can opt to pay for the database if you know you’ll need more space.

Once you get to the server setup page, this is where you can purchase more space or better performance. I am going to show the settings I opt for that provide a free and fast experience.

As for your cluster’s tier, I opt for the M0 cluster. This is free, and easy to scale. Although if your implementing this in an already profitable game I would recommend scaling to your needs.

Finally, choose a name!

After creating your cluster, you’ll be directed to the security tab. This will allow us to create logins so we can access the database later. Go ahead and make a root account by making the username “root” and the password anything you’d like. You can make multiple accounts if you plan to log the actions of specific administrators in your organization.


Make sure you remember your username and password, as this will be important later on.

Once you’ve added your login, we’ll be adding our IP addresses into the access list. Add the following IP adress (so that our Repl can access MongoDB): 0.0.0.0/0

At the bottom, click finish and close. Next click go to database.

Here we will click the connect button so we can connect to it through our Replit application.

You’ll want to click the “Connect your Application” button, and then make sure your settings are as follows.
image

Next, you’ll want to copy your connection SRV. Go ahead and copy it, it should look like this: mongodb+srv://root:password@tutorial-cluster.4hkyd.mongodb.net/?retryWrites=true&w=majority

Go ahead and find this part of the string root:password. You’ll want to replace password, and the <> with the password you made when creating your root account. After you’ve done this, go ahead and save that SRV somewhere as we’ll be using it to access the database on Replit.

Making the Replit Application

First we’ll navigate to the top bar and click sign-up.

Sign up with the same google account as earlier, to sign up quick and easy. Configure your display name, and bio as you wish and then finish setting up your account.

First thing you’ll want to do is navigate to the sidebar on the left side of your screen. At the top of that bar you’ll see a create button, go ahead and click that.
image

Choose the Node.js template, name the project, and create it! We’ll only need the default file to create the server as this will just be the messenger to our database.

Now we need to set up an environment variable. This is because the code will be public, so we need to hide the sensitive information from any nosy people that find your repl. So on the side bar, click the little lock icon and you should see this screen.
image

You need to title your variable (key field) “SRV”. For the value of it, paste in your SRV string we copied down earlier. Make sure you have put in the password correctly, or else we won’t be able to access the database from Replit.
image

Finally, click “Add new secret”.

Great! Now we can begin coding. This system works by having Roblox send a request to our Replit server, and then the Replit server will change the data in the database. To do this, we will need a couple different libraries. Express, and mongoose. Express will allow us to listen for the requests from Roblox and handle them, while mongoose will be the library connecting to MongoDB and changing the data. Replit automatically installs these libraries, so now we must require them! Start coding by requiring the libraries. Finally, we need body-parser. This is so that we can read the body of the requests made to our server.

const express = require("express")
const mongoose = require("mongoose")
const bodyParser = require("body-parser")

After you run the application, these will automatically be configured for you.

So now we need to start listening for requests by starting up a web server. Express makes this easy, just create a server variable using express’s built-in function. After we do that, we’ll start listening.

const app = express()
const listener = app.listen(3000, () => {
    console.log("APP | Application listening on port " + listener.address().port);
  });

In this code example, the application is listening on port 3000. If you run the app, it should now print to the console what port it’s listening on.
image

Now we need to tell our server what to do when it get’s a request. Right now we haven’t configured an endpoint so our application won’t be able to receive requests. We have to tell it specifically what URL’s to listen for, and what to do on each one. I am going to add one endpoint to find specific data, and an endpoint to save some data. Other endpoints will be taught in the sections below.

First things first, we need to tell our application to use the body-parser. We use this so that we can actually see the body (data) that is being sent in the request.

app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());

This code ensures that we will always be able to read the body of the requests sent to us. This is called middleware as it interacts with the requests in the middle of express processing it.

Now we will configure our two endpoints. Before we do, let’s go over the different types of requests you can have.

GET requests are the type of request you make when you want to get information from the server.
POST requests are the type of request you make when you want to add new information to the server.
PUT requests are the type of request you make when you want to edit information on the server.
DELETE requests are the type of request you make when you want to delete information off the server.

Now that we know the types of requests, it’s good to know that Roblox only supports GET and POST requests.

If you want to send data with your GET request (for example to find specific data); keep in mind Roblox will not let you send data with your GET requests so you should make a POST endpoint instead.

There are several functions you should use when creating an endpoint.

app.get()
app.post()
app.put()
app.delete()

We will start by creating a GET input to retrieve all of the data in our database.

app.get("/allData", async (request, response) => {
})

Before we start coding in the logic to retrieve and send back all of our data, let’s talk about what this function is doing. The first parameter is a string and shows the path off of the base URL that this code is listening for. For example if my base URL was “https://example.com/”, then this app would be listening for a GET request on “https://example.com/allData”.

Now we need to initiate a connection to mongoose. So you need to tell mongoose to connect and get ready to get the data.

mongoose.connect(process.env.SRV,{ useNewUrlParser: true, useUnifiedTopology: true });
var connection = mongoose.connection;

Our entire script should look like this so far.

const express = require("express")
const mongoose = require("mongoose")
const bodyParser = require("body-parser")

mongoose.connect(process.env.SRV,{ useNewUrlParser: true, useUnifiedTopology: true });
var connection = mongoose.connection;

const app = express()
const listener = app.listen(3000, () => {
    console.log("APP | Application listening on port " + listener.address().port);
  });

app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());

app.get("/allData", async (request, response) => {
  
})

Setting Up Models In Mongoose

So to code our first endpoint we will be simply retrieving all data in one of our models. Models are used to store specific types of data (Server model stores server information, player model stores player information etc.).

I am going to set-up a basic model for server information, you can have as many models as you’d like. Models are made based off of schemas, schemas will decide what data a model can have in it.

const serverSchema = new mongoose.Schema({
    playerCount: Number, 
    serverName: String
})  
const server = mongoose.model("Server", serverSchema)

Adding Endpoints

For the “allData” endpoint I want my application to retrieve all of the servers that I’ve saved information of in my database.

app.get("/allData", async (request, response) => {
    let data
    try {
-- Look in the database for all server data
      data = await mongoose.model("Server").find({})
    }
    catch(err) {
-- Catch any errors and log them
      console.log("DATABASE | Error: " + err)
    }
    if(data) {
-- Found the data, response with status 200 OK and send the data back
      response.status(200)
      response.json({
        status: "success",
        message: "200 | Servers found",
        data: data
      })
    }
    else {
      -- Couldn't find any data, return a 404 not found status
      response.status(404)
      response.json({
        status: "error",
        message: "404 | Servers not found"
      })
    }
})

And now, I’ll add a endpoint to save some data as a server.

  app.post("/addServer/", async (request, response) => {
    -- Read the request body to find the new servers data
    let playerCount = request.body.playerCount
    let serverName = request.body.serverName
    
    let data
    try {
      -- Check if another server already exsists
      data = mongoose.model("Server").findOne({ serverName: `${serverName}` }) 
    } catch (err) {
      console.log("DATABASE | Error: " + err)
    }
      -- Save the new server's data
      const serverModel = mongoose.model("Server");
      const newServer = new serverModel({
        playerCount: playerCount,
        serverName: serverName
      });
      newServer.save()
      -- Response with status 201 so they know it was created, send back the new server object
      response.status(201)
      response.json({
        status: "success",
        message: "201 | Server created",
        data: newServer
      })
  })

Here’s our whole script.

const express = require("express")
const mongoose = require("mongoose")
const bodyParser = require("body-parser")

mongoose.connect(process.env.SRV,{ useNewUrlParser: true, useUnifiedTopology: true }).then(console.log("Connected!"))
var db = mongoose.connection;

const app = express()
const listener = app.listen(3000, () => {
    console.log("APP | Application listening on port " + listener.address().port);
  });

app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());

const serverSchema = new mongoose.Schema({
    playerCount: Number, 
    serverName: String
})  
const server = mongoose.model("Server", serverSchema)

app.get("/allData", async (request, response) => {
    let data
    try {
      data = await mongoose.model("Server").find({})
    }
    catch(err) {
      console.log("DATABASE | Error: " + err)
    }
    if(data) {
      response.status(200)
      response.json({
        status: "success",
        message: "200 | Servers found",
        data: data
      })
    }
    else {
      response.status(404)
      response.json({
        status: "error",
        message: "404 | Servers not found"
      })
    }
})

  app.post("/addServer/", async (request, response) => {
    let playerCount = request.body.playerCount
    let serverName = request.body.serverName
      const serverModel = mongoose.model("Server");
      const newServer = new serverModel({
        playerCount: playerCount,
        serverName: serverName
      });
      newServer.save()
      response.status(201)
      response.json({
        status: "success",
        message: "201 | Server created",
        data: newServer
      })
  })

Making a Request From Roblox

Now that we’ve set up everything to handle our data, let’s make a request from Roblox.

In order to make a request in Roblox, we will have to paste this into a server script.

local http = game:GetService("HttpService")
http:RequestAsync({
   Url = "YourEndPointHere", -- Where to send the request
   Method =  "GET/POST", -- The Request Type
   Headers = {
      ["Content-Type"] = "application/json" -- Tells server that we are sending JSON in the body
   },
   Body = {} -- Actual Body of the request (must be encoded with JSON)
})

You will have to change the parameters to fit your needs. I will make an example request that first creates two servers, and then gets all the servers and prints them. Note: In order to get the URL to send the request to, take the base URL from the picture below and add in the endpoint. Eg. https://Tutorial.walkerunknown.repl.co/allData.

local http = game:GetService("HttpService")

local function addServer(body) 
	local res = http:RequestAsync({
		Url = "https://Tutorial.walkerunknown.repl.co/addServer", -- Where to send the request
		Method =  "POST", -- The Request Type
		Headers = {
			["Content-Type"] = "application/json" -- Tells server that we are sending JSON in the body
		},
		Body = http:JSONEncode(body) -- Actual Body of the request (must be encoded with JSON)
	})
	return res
end

local function getAllServers() 
	local res = http:RequestAsync({
		Url = "https://Tutorial.walkerunknown.repl.co/allData", -- Where to send the request
		Method =  "GET", -- The Request Type
		Headers = {
			["Content-Type"] = "application/json" -- Tells server that we are sending JSON in the body
		},
	})
	return res
end

local body = {
	playerCount = 2,
	serverName = "Server1"
}
addServer(body)
local body = {
	playerCount = 1,
	serverName = "Server2"
}
addServer(body)

local servers = getAllServers()
local decoded = http:JSONDecode(servers.Body)

for i,v in pairs(decoded.data) do
	print("Found "..v.serverName)
end

Expected output:
image

Securing the Server

At the moment, anyone who wishes to add or get data from our database can. For security, we’re going to simply add an authentication header to our request on Roblox. Next, we will check the headers on the server and make sure there is an authentication header and it has the right value. This will act as a sort of password to allow our Roblox game and server to interact with each other but prevent others from using our database.

First, let’s modify the request code I used earlier to include and authorization header.

local http = game:GetService("HttpService")
http:RequestAsync({
   Url = "YourEndPointHere", -- Where to send the request
   Method =  "GET/POST", -- The Request Type
   Headers = {
      ["Content-Type"] = "application/json" -- Tells server that we are sending JSON in the body,
      ["authorization"] = "AuthKey" -- Gives the "password" to the server
   },
   Body = {} -- Actual Body of the request (must be encoded with JSON)
})

Now, you’ll basically be able to create your password. Personally, since this doesn’t need to be remembered, I just smash my keyboard and make a random 50 digit string. Websites can also produce this for you. Once you have your key, head back to your Repl and create a new variable at the top of the script.

const auth = "AuthKeyHere"

Now, just add an if statement to check if the auth header matches the auth key as soon as the request is made.

if(request.headers.authorization === auth) {

}

Congrats! You’ve created your own external database!

Here are all of the scripts in there entire, completed form.

Replit Script
const express = require("express")
const mongoose = require("mongoose")
const bodyParser = require("body-parser")
const auth = "AuthKeyHere"

mongoose.connect(process.env.SRV,{ useNewUrlParser: true, useUnifiedTopology: true }).then(console.log("Connected!"))
var db = mongoose.connection;

const app = express()
const listener = app.listen(3000, () => {
    console.log("APP | Application listening on port " + listener.address().port);
  });

app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());

const serverSchema = new mongoose.Schema({
    playerCount: Number, 
    serverName: String
})  
const server = mongoose.model("Server", serverSchema)

app.get("/allData", async (request, response) => {
  if(request.headers.authorization === auth) {
    let data
    try {
      data = await mongoose.model("Server").find({})
    }
    catch(err) {
      console.log("DATABASE | Error: " + err)
    }
    if(data) {
      response.status(200)
      response.json({
        status: "success",
        message: "200 | Servers found",
        data: data
      })
    }
    else {
      response.status(404)
      response.json({
        status: "error",
        message: "404 | Servers not found"
      })
    }
  }
})

  app.post("/addServer/", async (request, response) => {
    if(request.headers.authorization === auth) {
    let playerCount = request.body.playerCount
    let serverName = request.body.serverName
      const serverModel = mongoose.model("Server");
      const newServer = new serverModel({
        playerCount: playerCount,
        serverName: serverName
      });
      newServer.save()
      response.status(201)
      response.json({
        status: "success",
        message: "201 | Server created",
        data: newServer
      })
    }
  })
Roblox Script
local http = game:GetService("HttpService")

local function addServer(body) 
	local res = http:RequestAsync({
		Url = "EndpointHere", -- Where to send the request
		Method =  "POST", -- The Request Type
		Headers = {
			["Content-Type"] = "application/json", -- Tells server that we are sending JSON in the body
			["authorization"] = "AuthKeyHere"
		},
		Body = http:JSONEncode(body) -- Actual Body of the request (must be encoded with JSON)
	})
	return res
end

local function getAllServers() 
	local res = http:RequestAsync({
		Url = "EndpointHere", -- Where to send the request
		Method =  "GET", -- The Request Type
		Headers = {
			["Content-Type"] = "application/json", -- Tells server that we are sending JSON in the body
			["authorization"] = "AuthKeyHere"
		},
	})
	return res
end

local body = {
	playerCount = 2,
	serverName = "Server1"
}
addServer(body)
local body = {
	playerCount = 1,
	serverName = "Server2"
}
addServer(body)

local servers = getAllServers()
local decoded = http:JSONDecode(servers.Body)

for i,v in pairs(decoded.data) do
	print("Found "..v.serverName)
end
Was this helpful?
  • Yes, thanks!
  • I’m confused.
  • Already knew this, thanks anyways!

0 voters

Last Update: 7/9/2022, Created: 7/9/2022

25 Likes

Honestly this is easier to do using Firebase REST API.

You can also just use Replit Database… Just make a nodejs project and click the database icon

Yes, you can do that as well. The benefit to using a database with MongoDB is that you can scale it easily, connect remotely via command prompt, more security, and it’s super easy to visualize your data. You can also use MongoDB compass to edit your data without code.

1 Like

Oh that’s pretty sick.

And also view it? Bc google firebase lets you view and edit data inside of the firebase console website.

There was a whole segment about it on Fox News. You should really stay up to date with current events.

They don’t sell the data though?

(Source: Privacy Policy - Replit)

Anyway, if you want to continue this I guess send me a DM.

Ive always stuck to Heroku instead of replit.

This tutorial was really valuable for a project I’m working on. Connected everything seamlessly to Heroku and stored the authorization token in config vars. Great tutorial, thanks!

2 Likes

This is a great tutorial! Recently Roblox added support for the remaining REST APIs (PUT, DELETE). After doing some research, I was able to add those APIs to the provided code in this tutorial, and wanted to share with others who are also looking for a similar solution.

Here is the full Replit script:

const express = require("express")
const mongoose = require("mongoose")
const bodyParser = require("body-parser")
const auth = "demo1"
const modelName = "Server"

// connect to SRV (mongoDB connection)
mongoose.connect(process.env.SRV,{ useNewUrlParser: true, useUnifiedTopology: true }).then(console.log("Connected!"))
var db = mongoose.connection;

// create replit server and port listener
const app = express()
const listener = app.listen(3000, () => {
  console.log("APP | Application listening on port " + listener.address().port);
});

// middleware
app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());

// schema setup
const schema = new mongoose.Schema({
  data: {
    playerCount: Number,
    serverName: String
  }
});
const model = mongoose.model(modelName, schema)

// get all data
app.get("/allData", async (request, response) => {
  // check for authorization
  if(request.headers.authorization === auth) {
    let data
    try {
      data = await mongoose.model(modelName).find({})
    }
    catch(err) {
      console.log("DATABASE | Error: " + err)
    }
    if(data) {
      // success
      response.status(200)
      response.json({
        status: "success",
        message: "200 | Found",
        data: data
      })
    }
    else {
      // could not find model
      response.status(404)
      response.json({
        status: "error",
        message: "404 | Not found"
      })
    }
  }
})

// add data
app.post("/addServer", async (request, response) => {
  // check for authorization
  if(request.headers.authorization === auth) {
    const model = mongoose.model(modelName);
    const newModel = new model(request.body);
    newModel.save()
    response.status(201)
    response.json({
      status: "success",
      message: "201 | Created",
      data: newModel
    })
    console.log(`New ${modelName} added`);
  }
})

// remove data
app.delete("/removeServer/:id", async (request, response) => {
  // check for authorization
  if (request.headers.authorization === auth) {
    try {
      // delete the data
      const deletedModel = await mongoose.model(modelName).findByIdAndDelete(request.params.id);
      if (deletedModel) {
        // success
        response.status(200)
        response.json({
          status: "success",
          message: "200 | Removed",
          data: deletedModel
        });
        console.log(`${modelName} removed`);
      } else {
        // could not find data
        response.status(404)
        response.json({
          status: "error",
          message: "404 | Not found"
        });
      }
    } catch (err) {
      // server error
      console.log("DATABASE | Error: " + err);
      response.status(500)
      response.json({
        status: "error",
        message: "500 | Internal Server Error"
      });
    }
  }
});

// update data
app.put("/updateServer/:id", async (request, response) => {
  // check for authorization
  if (request.headers.authorization === auth) {
    try {
      const id = request.params.id;
      const { data } = request.body;
      // perform the update function
      const updatedModel = await mongoose.model(modelName).findByIdAndUpdate(
        id,
        { data },
        { new: true }
      );
      if (updatedModel) {
        // success
        response.status(200)
        response.json({
          status: "success",
          message: "200 | Updated",
          data: updatedModel
        });
        console.log(`${modelName} updated`);
      } else {
        // could not find model
        response.status(404)
        response.json({
          status: "error",
          message: "404 | Not found"
        });
      }
    } catch (err) {
      // server error
      console.log("DATABASE | Error: " + err);
      response.status(500)
      response.json({
        status: "error",
        message: "500 | Internal Server Error"
      });
    }
  }
});

And the respective Roblox functions to request to each API:

local HEADERS = {
	["Content-Type"] = "application/json", -- sending in JSON format
	["authorization"] = AUTH_KEY
}

-- remove a server from our DB
local function removeServer(serverId)
	local res = HttpService:RequestAsync{
		Url = URL .. "removeServer/" .. serverId,
		Method = "DELETE",
		Headers = HEADERS
	}
	return res
end

-- update a server in our DB
local function updateServer(serverId, body)
	local res = HttpService:RequestAsync{
		Url = URL .. "updateServer/" .. serverId,
		Method = "PUT",
		Headers = HEADERS,
		Body = HttpService:JSONEncode(body)
	}
	return res
end

-- get the data
local data = HttpService:JSONDecode(addServer(body).Body).data)

-- modify the data to your needs
for i, v in pairs(decoded.data) do
	v.data.playerCount = 2
	v.data.serverName = "Server2"

	-- update the data
	local id = v._id
	updateServer(id, {data = v.data})
end

Note that this implementation pads the body with the following structure:

{
	data = {...}
}

This makes it easier to update the data as it avoids the ._id and .__v keys automatically added by MongoDB, therefore allowing you to directly edit the returned body.data from the GET request because that’s all you have to pass in when sending the PUT request (otherwise you would send in duplicates of ._id and .__v).

1 Like

Shouldnt u put this in a repl secret?

1 Like