Skip to content

Creating a new Server (SCE CLI Version)

Evan Ugarte edited this page Jun 18, 2024 · 2 revisions

Note: This is for running the website with the sce tool SCE-CLI. If you are running with npm, follow this backend tutorial instead.

Looking at our architecture diagram on the Our Code Structure wiki page (link), we have different servers running in SCE's backend. "This is complicated 😭! How does it work 🤔?" you ask. The best way to learn is if you create another one on your own.

Before starting this tutorial

  • You followed along with this simple express video
  • Core-v4 is cloned and set up on your computer (see the "Getting Started" wiki page

General Layout of an SCE Server

One of the four servers (as of 12/11/21) is the logging_api (see api/logging_api). Clicking on the link, you should see the below layout

image

Explanation of each compoment

Ignoring the Dockerfile, we see models, routes and server.js. In a nutshell:

  • models is for defining MongoDB Schemas (if any)
  • routes is for defining Express HTTP request handlers
  • server.js runs the server itself.

Let's create our own API to understand each in depth!

Creating Your Own Model

SCE's backend makes use of the Mongoose library for interacting with MongoDB. Below is an example of creating said schema. For further details on schemas, check out the Mongoose docs.

Let's say we want to create a collection of different Animals. Each Animal in the collection will store information such as the Name, Description and a lifespan in years. Turning this into a schema, we get:

api/main_endpoints/models/Animal.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const AnimalsSchema = new Schema(
  {
    name: {
      type: String,
      required: true
    },
    description: {
      type: String,
      default: 'No description provided.'
    },
    lifespan: {
        type: Number,
    }
  },
  { collection: 'Animals' }
);

module.exports = mongoose.model('Animals', AnimalsSchema);

Defining HTTP Request Handlers

We now need to define handlers for our MongoDB Collection to create, read, update and delete data. Create a new file called Animal.js from a new directory called routes, within your api directory.

Imports

At the top of your file, we need to import some predefined variables and libraries. api/main_endpoints/routes/Animal.js

const express = require('express');
const router = express.Router();
const Animal = require('../models/Animal');
const {
  OK,
  BAD_REQUEST,
  NOT_FOUND
} = require('../../util/constants').STATUS_CODES;

The above snippet imports express and the Animal schema we defined above so we can interact with the Animal collection in MongoDB.

Creating Data

The below handler takes a HTTP POST request and adds data from the request into MongoDB (we will test this with postman later). The data is part of req.body and has information for each field for the Animal schema. We return the added document if the insert was successful or a 400 (BAD_REQUEST) if not.

router.post('/createAnimal', (req, res) => {
  const { lifespan } = req.body;
  const numberSent = !Number.isNaN(Number(lifespan));

  const newEvent = new Animal({
    name: req.body.name,
    description: req.body.description,
    lifespan: numberSent ? Number(lifespan) : undefined,
  });

  Animal.create(newEvent, (error, post) => {
    if (error) {
      return res.sendStatus(BAD_REQUEST);
    } else {
      return res.json(post);
    }
  });
});

Reading Data

The below handler takes an HTTP GET request and returns all documents in the Animal collection.

router.get('/getAnimals', (req, res) => {
  Animal.find()
    .then(items => res.status(OK).send(items))
    .catch(error => {
      res.sendStatus(BAD_REQUEST);
    });
});

Updating Data

This handler is similar to the createAnimal above, but instead of creating a document, it uses the ID of an existing document to update its data. The parameters sent in the request are extracted in the first lines of the function and are used to optionally update each of the document's fields.

router.post('/editAnimal', (req, res) => {
  const {
    name,
    description,
    lifespan,
    _id,
  } = req.body;
  Animal.findOne({ _id })
    .then(Animal => {
      Animal.name = name || Animal.name;
      Animal.description = description || Animal.description;
      Animal.lifespan = lifespan || Animal.lifespan;
      Animal
        .save()
        .then(() => {
          res.sendStatus(OK);
        })
        .catch(() => {
          res.sendStatus(BAD_REQUEST);
        });
    })
    .catch(() => {
      res.sendStatus(NOT_FOUND);
    });
});

Deleting Data

Below is a function that takes an HTTP post request. It expects one parameter in the JSON body which is the MongoDB ID of the document that is to be deleted.

router.post('/deleteAnimal', (req, res) => {
  Animal.deleteOne({ _id: req.body._id })
    .then(result => {
      if (result.n < 1) {
        res.sendStatus(NOT_FOUND);
      } else {
        res.sendStatus(OK);
      }
    })
    .catch(() => {
      res.sendStatus(BAD_REQUEST);
    });
});
http://localhost:8080/api/Animal/createAnimal
http://localhost:8080/api/Animal/getAnimals
http://localhost:8080/api/Animal/editAnimal
http://localhost:8080/api/Animal/deleteAnimal

Testing With Postman

Postman allows us to send HTTP requests in a sandbox environment to our APIs (here is the download page.

Before testing, your api directory should now have a layout like:

api/
├── models/
│   ...other files
│   └── Animal.js
├── routes/
|   ...other files
│   └── Animal.js
└── server.js

Creating a Document

Let's add a Dog to our database. Call our create handler with a JSON body of

{
    "name": "Dog"
}

Below is the result. Our code should insert the data and return the created MongoDB document. Notice our description default value worked and since we didn't add a lifespan, the document doesn't have the field!

image

Try adding another animal with a description and lifespan and compare the results.

Reading all Documents

Let's call our read handler to get all of our Animals in MongoDB. We should get an array of documents like below: image

Updating a Document

Below we update the description of a document, passing in its MongoDB ID. image

Rerun the read handler above to ensure the data updated.

Deleting a Document

Below deletes a document by its MongoDB ID. Rerun the read handler above to ensure the data updated.

image