How to create CRUD applications with Azure Functions and MongoDB
Learn how to make a CRUD application with Azure Functions integrated with MongoDB and Node.js in this helpful how-to guide.
Jun 08, 2023 • 10 Minute Read
In this article, we'll be learning how we can make a CRUD application with Azure Functions and integrated with MongoDB and Node.js
You can develop Serverless applications with Azure Functions in loads of programming languages: C#, JavaScript, F#, Java, PowerShell, Python, TypeScript, and other languages such as Go, Rust & R. However, for this post we’ll focus on JavaScript. Let's get started!
1. Install Azure Functions Core Tools package
Azure Functions Core Tools will allow us to develop and test functions locally on our machine from a terminal or command prompt. Here are the links to programs and packages I used to solve this challenge:
The following assumes that version X of Node.js is installed on the machine.
- Windows
npm install -g azure-functions-core-tools
- MacOS
brew tap azure/functions
brew install azure-functions-core-tools
- Linux (Ubuntu/Debian) with APT
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
For more information on how to correctly install Azure Functions Core Tools, visit the link HERE.
To verify that Azure Functions Core Tools is installed correctly on your machine, check for the func command on the terminal:
> func
If it happens according to the GIF below, it’s because the package was installed successfully!
Great. Now we can create our functions. To do this, create a local folder on your machine — and let's get started!
2. Begin creating your new application
Now that we have installed the package, let's create a new application. For this, just follow the steps as the GIF below.
Note that when we open Visual Studio Code, we need to click the YES
button that appears in the bottom right corner to enable some important files in the project.
3. Create the MongoDB connection
Now let's make some necessary changes to our newly created project. With this, we will install MongoDB locally in our project. Enter the command:
npm install mongodb
When we installed MongoDB in the project, note that there were changes in the package.json
file. The file will look as below:
- file: package.json
{
"name": "crud-serverless-mongodb",
"version": "1.0.0",
"description": "Challenge-4 25 days of serverless",
"scripts": {
"test": "echo "No tests yet...""
},
"author": "",
"dependencies": {
"mongodb": "^3.3.2"
}
}
Now let's create a folder called: shared
and inside it we will create the file: mongo.js
. The project structure will now as you can see below:
- file: shared/mongo.js
/**
* Arquivo: mongo.js
* Data: 01/25/2021
* Descrição: file responsible for handling the database connection locally
* Author: Glaucia Lemos – (Twitter: @glaucia_lemos86)
*/
const { MongoClient } = require("mongodb");
const config = {
url: "mongodb://localhost:27017/crud-serverless-mongodb",
dbName: "crud-serverless-mongodb"
};
async function createConnection() {
const connection = await MongoClient.connect(config.url, {
useNewUrlParser: true
});
const db = connection.db(config.dbName);
return {
connection,
db
};
}
module.exports = createConnection;
Here we are creating our local connection to MongoDB!
And let's also change the local.settings.json
file. This file is responsible for “storing” all keys that we do not want to be exposed when commit to GitHub. Note that this file is in the file list in .gitignore
.
Open the local.settings.json
file and make some changes:
- file: local.settings.json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "{AzureWebJobsStorage}"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
}
}
See in the code above that we are already enabling CORS
. Because without it, we can’t perform CRUD operations! If you’d like to understand a little bit more about CORS, I recommend this reading HERE.
Great! The first step is already finished it. Now let's create our CRUD in Azure Functions!
NoSQL for Grownups: DynamoDB Single-Table Modeling w/ Rick Houlihan
DynamoDB can be a scalable, cost-effective replacement for a traditional relational database . . . if you use it correctly! In this free on-demand webinar, Rick Houlihan, Sr. Practice Manager at AWS and inventor of single-table DynamoDB design, shows his tricks for modeling complex data access patterns in DynamoDB.
4. Function - 'CreateDish'
To create a new function just type the following command:
func new
When you enter this command, it will give you several template options that Azure Functions makes available to us. Let's choose HttpTrigger template.
Note that a CreateDish folder and two files were created:
- function.json: here we'll define the routes and endpoint methods.
- index.json: here we'll develop endpoint logic.
Let's start to change these files. Starting with function.json
- file: CreateDish/function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post"],
"route": "dishes"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
Now let's change the index.js file:
- file: CreateDish/index.js
/**
* File: CreateDish/index.js
* Description: file responsible for creating a new 'Dish'
* Date: 01/25/2021
* Author: Glaucia Lemos (Twitter: @glaucia_lemos86)
*/
const createMongoClient = require('../shared/mongo');
module.exports = async function (context, req) {
const dish= req.body || {}
if (dish) {
context.res = {
status: 400,
body: 'Dish data is required! '
}
}
const { db, connection } = await createMongoClient()
const Dishes = db.collection('dishes')
try {
const dishes = await Dishes.insert(dish)
connection.close()
context.res = {
status: 201,
body: dishes.ops[0]
}
} catch (error) {
context.res = {
status: 500,
body: 'Error creating a new Dish'
}
}
}
Here we’re defining the Post
route and developing the logic for: Create a New Dish
.
Let's run this endpoint! To run, just type the command:
func host start
And it will list our created endpoint! Look at the GIF below.
It lists for us the following endpoint: [POST]
http://localhost:7071/api/dishes
5. Function - 'GetAllDishes'
It's the same thing we did above. Let's create a new function with the command: func new, include the the function name as GetAllDishes and change the files: function.json and index.js
- GetAllDishes/function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "dishes"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- GetAllDishes/index.js
/**
* File: GetAllDishes/index.js
* Description: file responsible for list all 'Dishes'
* Data: 01/25/2021
* Author: Glaucia Lemos (Twitter: @glaucia_lemos86)
*/
const createMongoClient = require('../shared/mongo')
module.exports = async context => {
const { db, connection } = await createMongoClient()
const Dishes = db.collection('dishes')
const res = await Dishes.find({})
const body = await res.toArray()
connection.close()
context.res = {
status: 200,
body
}
}
6. Function - 'GetDishById'
Now that it’s clear how easy it is to create a CRUD with Azure Functions, I’ll start speeding up the creation process and just inform you what has changed in the function.json
and index.js
files:
- GetDishById/function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "dishes/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- GetDishById/index.js
// @ts-nocheck
/**
* File: GetDishById/index.js
* Description: file responsible for get a 'Dish' by Id
* Data: 01/25/2021
* Author: Glaucia Lemos (@glaucia_lemos86)
*/
const { ObjectID } = require('mongodb')
const createMongoClient = require('../shared/mongo')
module.exports = async function (context, req) {
const { id } = req.params
if (!id) {
context.res = {
status: 400,
body: 'Please enter the correct Dish Id number!'
}
return
}
const { db, connection } = await createMongoClient()
const Dishes = db.collection('dishes')
try {
const body = await Dishes.findOne({ _id: ObjectID(id) })
connection.close()
context.res = {
status: 200,
body
}
} catch (error) {
context.res = {
status: 500,
body: 'Error listing Dish by Id.'
}
}
}
7. Function - 'UpdateDishById'
- UpdateDishById/function.json
{
"bindings": [{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["put"],
"route": "dishes/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- UpdateDishById/index.js
// @ts-nocheck
/**
* File: UpdateDishById/index.js
* Description: file responsible for update a 'Dish' by Id
* Data: 01/25/2021
* Author: Glaucia Lemos (@glaucia_lemos86)
*/
const { ObjectID } = require('mongodb')
const createMongoClient = require('../shared/mongo')
module.exports = async function (context, req) {
const { id } = req.params
const dish = req.body || {}
if (!id || !dish) {
context.res = {
status: 400,
body: 'Fields are required'
}
return
}
const { db, connection } = await createMongoClient()
const Dishes = db.collection('dishes')
try {
const dishes = await Dishes.findOneAndUpdate(
{ _id: ObjectID(id) },
{ $set: dish }
)
connection.close()
context.res = {
status: 200,
body: dishes
}
} catch (error) {
context.res = {
status: 500,
body: 'Error updating a Dish'
}
}
}
8. Function -'DeleteDishById'
Above the 'DeleteBishById code:
- DeleteDishById/function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["delete"],
"route": "dishes/{id}"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
- DeleteDishById/index.js
// @ts-nocheck
/**
* File: DeleteDishById/index.js
* Description: file responsible for delete a 'Dish' by Id
* Data: 01/25/2021
* Author: Glaucia Lemos (Twitter: @glaucia_lemos86)
*/
const { ObjectID } = require('mongodb')
const createMongoClient = require('../shared/mongo')
module.exports = async function (context, req) {
const { id } = req.params
if (!id) {
context.res = {
status: 400,
body: 'The fields are required!'
}
return
}
const { db, connection } = await createMongoClient()
const Dishes = db.collection('dishes')
try {
await Dishes.findOneAndDelete({ _id: ObjectID(id) })
connection.close()
context.res = {
status: 204,
body: 'Dish deleted successfully!'
}
} catch (error) {
context.res = {
status: 500,
body: 'Error Deleting Dish' + id
}
}
}
And our CRUD is ready! Let's test all endpoints! To test, open Postman, you can download POSTMAN - HERE and include the json request. See the example below:
{
"name": "Amanda",
"dish": "garlicky green beans",
"vegetarian": true,
"vegan": false,
"allergens": "nuts (almonds)"
}
- Create a new dish: [POST] http://localhost:7071/api/dishes/
- List all dishes: [GET] http://localhost:7071/api/dishes/
- List dishes by Id: [GET] http://localhost:7071/api/dishes/{id}
- Update dish by Id: [PUT] http://localhost:7071/api/dishes/{id}
- Delete dish by Id: [DELETE] http://localhost:7071/api/dishes/{id}
Learn more about Azure Fundamentals
If you want to learn more about Azure Functions, Microsoft offers free courses and eBooks to help you to learn more about serverless and Azure Functions:
- Azure SDK for JavaScript documentation
- Azure Functions documentation
- Tutorial: Create and deploy serverless Azure Functions in Python with Visual Studio Code
- Free eBook - Azure Serverless Computing Cookbook
- Free Courses Microsoft Learn - Azure Functions
- Free Course – Build JavaScript Applications with Node.js
And if you want to know more about other technology news, Azure and JavaScript / Node.Js, be sure to follow me on Twitter!