|
| 1 | +# Introduction To Database Schemas |
| 2 | + |
| 3 | +A database schema is a blueprint or architecture of how our data will look. It doesn’t hold data itself, but instead describes the shape and structure of the data and how it might be related to other data. |
| 4 | + |
| 5 | +For example, this is a `customer` schema in MongoDB using Mongoose. |
| 6 | + |
| 7 | +```js |
| 8 | +const mongoose = require("mongoose"); |
| 9 | + |
| 10 | +const customer = new mongoose.Schema({ |
| 11 | + name: { |
| 12 | + type: String, |
| 13 | + required: true, |
| 14 | + }, |
| 15 | + zipcode: { |
| 16 | + type: Number, |
| 17 | + }, |
| 18 | +}); |
| 19 | + |
| 20 | +module.exports = mongoose.model("customer", customer); |
| 21 | +``` |
| 22 | + |
| 23 | +The same `customer` schema in PostgreSQL using [Sequelize](https://sequelize.org/). |
| 24 | + |
| 25 | +```js |
| 26 | +const Sequelize = require("sequelize"); |
| 27 | + |
| 28 | +module.exports = (sequelize, DataTypes) => { |
| 29 | + const Customer = sequelize.define("customer", { |
| 30 | + name: { |
| 31 | + type: DataTypes.STRING, |
| 32 | + allowNull: false, |
| 33 | + }, |
| 34 | + zipcode: { |
| 35 | + type: DataTypes.INTEGER, |
| 36 | + }, |
| 37 | + }); |
| 38 | + return Customer; |
| 39 | +}; |
| 40 | +``` |
| 41 | + |
| 42 | +We can see that the schema definition for the `customer` has a `name` that is type `string` and a `zipcode` that is of type `number`. The `name` field is mandatory and cannot be empty. |
| 43 | + |
| 44 | +Another concept that can be confused with the database schema is the "Model". |
| 45 | +Models are fancy constructors compiled from schema definitions. An instance of a model is called a document. Models are responsible for creating and reading documents from the underlying MongoDB database. |
| 46 | + |
| 47 | +## Introduction to CRUD operations |
| 48 | + |
| 49 | +When we are building APIs, we want our models to provide four basic types of functionality. The model must be able to Create, Read, Update, and Delete resources. Computer scientists often refer to these functions by the acronym CRUD. A model should have the ability to perform at most these four functions in order to be complete. |
| 50 | + |
| 51 | +### CRUD in REST |
| 52 | + |
| 53 | +In a REST environment, CRUD often corresponds to the [HTTP request methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) `POST`, `GET`, `PUT`, and `DELETE`, respectively. These are the fundamental elements of a persistent storage system. |
| 54 | + |
| 55 | +For example, imagine we are working with a system that is keeping track of meals and their corresponding prices for a restaurant. Let’s look at how we would implement CRUD operations. |
| 56 | + |
| 57 | +First of all, we need to define our food item schema (we will use mongoose for that), and let's call it "dish". |
| 58 | + |
| 59 | +```js |
| 60 | +const mongoose = require("mongoose"); |
| 61 | + |
| 62 | +const dish = new mongoose.Schema({ |
| 63 | + title: { |
| 64 | + type: String, |
| 65 | + required: true, |
| 66 | + }, |
| 67 | + image: { |
| 68 | + type: String, |
| 69 | + required: true, |
| 70 | + }, |
| 71 | + tags: [String], |
| 72 | +}); |
| 73 | + |
| 74 | +module.exports = mongoose.model("dish", dish); |
| 75 | +``` |
| 76 | + |
| 77 | +#### Create |
| 78 | + |
| 79 | +To create resources in a REST environment, we most commonly use the HTTP POST method. POST creates a new resource of the specified resource type. |
| 80 | +Imagine that we are adding a new food item to the stored list of dishes for this restaurant, and the dish objects are stored in a dishes resource. |
| 81 | + |
| 82 | +Note: Use `POST` when you want to add a child resource under resources collection. |
| 83 | + |
| 84 | +Request: |
| 85 | + |
| 86 | +`POST http://www.myrestaurant.com/api/dishes/` |
| 87 | + |
| 88 | +We will need to send the dish data too as an object in the body. |
| 89 | + |
| 90 | +For example |
| 91 | + |
| 92 | +```js |
| 93 | +{ |
| 94 | + title: "Lasagna", |
| 95 | + image: "www.food.com/lasagna.jpg", |
| 96 | + tags: ["simple", "classic"] |
| 97 | +} |
| 98 | + |
| 99 | +``` |
| 100 | + |
| 101 | +On the backend side, our express route will be |
| 102 | + |
| 103 | +```js |
| 104 | +router.post("/dishes", async (req, res) => { |
| 105 | + // get the data we sent in the body |
| 106 | + const dishData = req.body; |
| 107 | + try { |
| 108 | + // using the create() method with Mongoose will insert the data we sent in the body to our MongoDB database |
| 109 | + // and return an object of that data when done |
| 110 | + const newDish = await DishModel.create(dishData); |
| 111 | + // then, we return the new created dish object |
| 112 | + res.status(201).json(newDish); |
| 113 | + } catch (err) { |
| 114 | + res.status(422).json({ message: err.message }); |
| 115 | + } |
| 116 | +}); |
| 117 | +}) |
| 118 | +``` |
| 119 | + |
| 120 | +#### Read |
| 121 | + |
| 122 | +To read resources in a REST environment, we use the GET method. In practice, reading a resource shouldn't change any information - it should only retrieve it. |
| 123 | + |
| 124 | +To read resources in a REST environment, we use the GET method. Reading a resource should never change any information - it should only retrieve it. REST itself is more like a set of guidelines. Technically, you can change data in a `GET` request, but since we are creating a RESTful API, you shouldn't do that. Having a GET request that updates data in your database would be confusing to the users of your API and violate the expected behavior of REST. |
| 125 | + |
| 126 | +Request: |
| 127 | + |
| 128 | +`GET http://www.myrestaurant.com/api/dishes/` |
| 129 | + |
| 130 | +The express route for getting all dished will be |
| 131 | + |
| 132 | +```js |
| 133 | +router.get("/dishes", async (_, res) => { |
| 134 | + // here, we are using the find() to return all dishes from our MongoDB database |
| 135 | + // find() will return an array of dish objects |
| 136 | + const posts = await DishModel.find(); |
| 137 | + res.json(posts); |
| 138 | +}); |
| 139 | +``` |
| 140 | + |
| 141 | +#### Update |
| 142 | + |
| 143 | +PUT is the HTTP method used for the CRUD operation, Update. |
| 144 | +So if the price of Avocado Toast has gone up, we should go into the database and update that information using PUT. |
| 145 | + |
| 146 | +Note: Use `PUT` when you want to modify a singular resource which is already a part of resources collection. `PUT` replaces the resource with the data you send in its entirety. |
| 147 | + |
| 148 | +Request: |
| 149 | + |
| 150 | +`PUT http://www.myrestaurant.com/dishes/:id` |
| 151 | + |
| 152 | +We will need to send the new dish data too as an object. |
| 153 | + |
| 154 | +For example |
| 155 | + |
| 156 | +```js |
| 157 | +{ |
| 158 | + title: "New dish title", |
| 159 | + image: "www.food.com/dish.jpg", |
| 160 | + tags: ["simple", "classic"] |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +Note that when using Mongoose you don't have to send all the object fields, you can only send the ones that got changed. |
| 165 | + |
| 166 | +For example, we can only send the title. |
| 167 | + |
| 168 | +```js |
| 169 | +{ |
| 170 | + title: "New dish title", |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +Respectively, our express route for both these examples will be |
| 175 | + |
| 176 | +```js |
| 177 | +router.put("/dishes/:id", async (req, res) => { |
| 178 | + // we need the dish id in order to tell Mongoose which dish to update in our database |
| 179 | + const { id } = req.params; |
| 180 | + try { |
| 181 | + // the findByIdAndUpdate() will make sure the dish exist before updating it |
| 182 | + const updatedDish = await DishModel.findByIdAndUpdate(id, req.body, { |
| 183 | + // the { new: true } here is just to tell Mongoose we want the newely updated dish back |
| 184 | + new: true, |
| 185 | + }); |
| 186 | + // we return the updated dish as a JSON object to the user |
| 187 | + res.json(updatedDish); |
| 188 | + } catch (err) { |
| 189 | + res.status(422).json({ message: err.message }); |
| 190 | + } |
| 191 | +}); |
| 192 | +``` |
| 193 | + |
| 194 | +#### Delete |
| 195 | + |
| 196 | +The CRUD operation Delete corresponds to the HTTP method DELETE. It is used to remove a resource from the system. |
| 197 | +Let’s say that the world avocado shortage has reached a critical point, and we can no longer afford to serve this modern delicacy at all. We should go into the database and delete the item that corresponds to **Avocado Toast**, which we know has an `id` of 1223. |
| 198 | + |
| 199 | +Request: |
| 200 | + |
| 201 | +`DELETE http://www.myrestaurant.com/dishes/:id` |
| 202 | + |
| 203 | +The express route for this deleting request can be |
| 204 | + |
| 205 | +```js |
| 206 | +router.delete("/dishes/:id", async (req, res) => { |
| 207 | + const { id } = req.params; |
| 208 | + try { |
| 209 | + const dish = await PostModel.findByIdAndDelete(id); |
| 210 | + // the status code is 204 because we are not returning data or entities |
| 211 | + return res.status(204).json({ success: true }); |
| 212 | + } catch (err) { |
| 213 | + res.status(422).json({ message: err.message }); |
| 214 | + } |
| 215 | +}); |
| 216 | +``` |
| 217 | + |
| 218 | +--- |
| 219 | + |
| 220 | +## References |
| 221 | + |
| 222 | +- https://restfulapi.net/ |
| 223 | +- https://www.educative.io/blog/crud-operations#what |
| 224 | +- https://www.educative.io/blog/what-are-database-schemas-examples#types |
0 commit comments