DEV Community

loading...
Cover image for Creating CRUD with Azure Functions & MongoDB
Microsoft Azure

Creating CRUD with Azure Functions & MongoDB

glaucia86 profile image Glaucia Lemos Updated on ・9 min read

Red-and-White-Dating-Presentation.jpg

This article is part of #25DaysOfServerless. New challenges will be published every day from Microsoft Cloud Advocates throughout the month of December. Find out more about how Microsoft Azure enables your Serverless functions.

Have an idea or a solution?

Here in Brooklyn, NY, Ezra wants to have a big holiday potluck before everyone travels home for the holidays! His tiny apartment can barely fit everyone in, but it's a cozy way to celebrate with friends. He usually uses an online spreadsheet to coordinate who's bringing what, to make sure there's varieties of food to meet all dietary needs.

But the grinch stole all the servers! So Ezra can't do that this year.

Build an HTTP API that lets Ezra's friends add food dishes they want to bring to the potluck, change or remove them if they need to (plans change!), and see a list of what everybody's committed to bring.


Some Important Tips! 📌

To solve this challenge you don't need to use JavaScript/Node, you can develop in another programming language, including:

However, for this article we will focus on JavaScript! 😉

Installing 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's 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
Enter fullscreen mode Exit fullscreen mode
  • macOS
brew tap azure/functions
brew install azure-functions-core-tools
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

For more information on how to correctly install Azure Functions Core Tools, just 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
Enter fullscreen mode Exit fullscreen mode

If it happens according to the gif below it is because the package was installed successfully!

gif-serverless-07.gif

Great. Now we can create our functions. To do this, create a local folder on your machine and let's get started!

Let's Get Started!

Now that we have installed the package, let's create a new application. For this, just follow the steps as the gif below:

gif-serverless-08.gif

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.

Creating 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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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: 12/04/2019
 * Descrição: file responsible for handling the database connection locally
 * Author: Glaucia Lemos
 */

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;
Enter fullscreen mode Exit fullscreen mode

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": "*"
  }
}
Enter fullscreen mode Exit fullscreen mode

See in the code above that we are already enabling CORS. Because without it, we cannot perform CRUD operations! If you would 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!

Function - 'CreateDish'

To create a new function just type the following command:

func new
Enter fullscreen mode Exit fullscreen mode

When you enter this command 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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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: 11/16/2019
 * Author: Glaucia Lemos (@glaucia86)
 */

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'
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we are 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
Enter fullscreen mode Exit fullscreen mode

And it will list our created endpoint! Look at the gif:

gif-serverless-11.gif

It lists for us the following endpoint: [POST] http://localhost:7071/api/dishes

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 andindex.js

  • GetAllDishes/function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"],
      "route": "dishes"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
  • GetAllDishes/index.js
/**
 * File: GetAllDishes/index.js
 * Description: file responsible for list all 'Dishes'
 * Data: 11/16/2019
 * Author: Glaucia Lemos (@glaucia86)
 */

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
  }
}
Enter fullscreen mode Exit fullscreen mode

Function - 'GetDishById'

Now that it is very clear to everyone here how easy it is to create a CRUD with Azure Functions, I will start speeding up the creation process and just inform you what has changed in the function.json andindex.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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
  • GetDishById/index.js
// @ts-nocheck
/**
 * File: GetDishById/index.js
 * Description: file responsible for get a 'Dish' by Id
 * Data: 11/16/2019
 * Author: Glaucia Lemos (@glaucia86)
 */

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.'
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Function - 'UpdateDishById'

  • UpdateDishById/unction.json
{
    "bindings": [{
            "authLevel": "anonymous",
            "type": "httpTrigger",
            "direction": "in",
            "name": "req",
            "methods": ["put"],
            "route": "dishes/{id}"
        },
        {
            "type": "http",
            "direction": "out",
            "name": "res"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode
  • UpdateDishById/index.js
// @ts-nocheck
/**
 * File: UpdateDishById/index.js
 * Description: file responsible for update a 'Dish' by Id
 * Data: 11/16/2019
 * Author: Glaucia Lemos (@glaucia86)
 */

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'
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
  • DeleteDishById/index.js
// @ts-nocheck
/**
 * File: DeleteDishById/index.js
 * Description: file responsible for delete a 'Dish' by Id
 * Data: 11/16/2019
 * Author: Glaucia Lemos (@glaucia86)
 */

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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)"
}
Enter fullscreen mode Exit fullscreen mode
  • 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}

Next Steps?!

If you want to learn more about Azure Functions, we've free courses and e-books may help you to learn more about Serverless & Azure Functions:


Want to submit your solution to this challenge? Build a solution locally and then submit an issue. If your solution doesn't involve code you can record a short video and submit it as a link in the issue desccription. Make sure to tell us which challenge the solution is for. We're excited to see what you build! Do you have comments or questions? Add them to the comments area below.


Watch for surprises all during December as we celebrate 25 Days of Serverless. Stay tuned here on dev.to as we feature challenges and solutions! Sign up for a free account on Azure to get ready for the challenges!

See ya!

Discussion

pic
Editor guide
Collapse
carlosen14 profile image
CarlosV

Little mistake on 'Function - 'UpdateDishById' file'

{
    "bindings": [{
            "authLevel": "anonymous",
            "type": "httpTrigger",
            "direction": "in",
            "name": "req",
            "methods": ["put"],
            "route": "funcionarios/{id}" <<<<<<<<<<<< funcionarios?
        },
        {
            "type": "http",
            "direction": "out",
            "name": "res"
        }
    ]
}
Collapse
glaucia86 profile image
Glaucia Lemos Author

Desculpe Carlos. Não entendi. Executou e atualizou para mim aqui. Onde está o erro?
Teria como você abrir uma issue no repositório desse desafio e mandar um gif de q não está funcionando?

Collapse
carlosen14 profile image
CarlosV

Line..

"route": "funcionarios/{id}"

should be

"route": "dishes/{id}"

Thread Thread
glaucia86 profile image
Glaucia Lemos Author

ahhh show! É porque foi replicação de uma outra aplicação usando Azure Functions que eu tinha feito. Atualizando aqui. Obrigada por avisar!