-
Notifications
You must be signed in to change notification settings - Fork 15
Creating a new Server (SCE CLI Version)
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.
- You followed along with this simple express video
- Core-v4 is cloned and set up on your computer (see the "Getting Started" wiki page
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
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!
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);
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.
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.
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);
}
});
});
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);
});
});
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);
});
});
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
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
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!
Try adding another animal with a description and lifespan and compare the results.
Let's call our read handler to get all of our Animals in MongoDB. We should get an array of documents like below:
Below we update the description of a document, passing in its MongoDB ID.
Rerun the read handler above to ensure the data updated.
Below deletes a document by its MongoDB ID. Rerun the read handler above to ensure the data updated.