DEV Community

Cover image for Basic CRUD API with express
Michael Otu
Michael Otu

Posted on

Basic CRUD API with express

Content

  • Introduction
  • Project creation and initialization
  • Create a simple server and a GET route
  • Routes and Request handlers
  • Request and Response
  • Watch for changes
  • Create POST, GET, UPDATE, and DELETE routes
  • API Clients
  • Request body, params, query, header, ...
  • Manipulating memory data
  • Conclusion

Introduction







In JavaScript Essentials: Part 7, I hinted that we'd look into developing APIs ([0] [1]) and this is where we start. We will have a taste of what it takes to develop a simple API to track expenditures.

Project Description

For this expense tracker, we will need to keep track of the item purchased, the amount and the date the item was purchased. An expense will look like this:

{
    "name": "Legion Tower 7i Gen 8 (Intel) Gaming Desktop",
    "amount": 2099.99,
    "date": "2024-31-12"
}
Enter fullscreen mode Exit fullscreen mode

At this point, since a real database has not been added yet, we can use a list (an array) to hold the data that we will create. In this excerpt, we will go through each major concept surrounding creating APIs and add some ways to improve this app later.

Know that we will be building on top of this project, so, keep it clean and explore, experiment, fidget, etc as much as you can.

Project creation and initialization

As usual, we need a fresh working environment for each project, so we will begin by creating and initializing a Node JS project. If you are not sure, check out JavaScript Essentials: Part 6 (Mastermind in Javascript).

Now we have to create the parent folder for our API by doing:

# create a folder for the project at the path of your choice
mkdir expense-tracker-simple-api

# open the project with vscode
# code expense-tracker-simple-api
code -r expense-tracker-simple-api

# open the built-in terminal and init node
npm init -y
# this should create the package.json file

# create the entry file, index.js
echo "console.log(\"expense-tracker-simple-api\");" > index.js

# run the index.js file
node index.js
Enter fullscreen mode Exit fullscreen mode

All we are doing with this script is quite direct.

  • We create a folder for our project
  • We opened the folder in vscode
  • We initialized a nodejs project
  • We added a console log into the index.js file. This creates the file and adds some content
  • We execute the index.js file

An alternative is to go to wherever you want to create this folder and create it over there then open the folder in vscode and init node project - check out, JavaScript Essentials: Part 6 (Mastermind in Javascript).

At this point, we need to install a nodejs package called express. Express is a library that will help us create our APIs.

We can install this package by running, npm i express. This should modify the package.json file, and create the package-lock.json file and node_modules folder. Consult the excerpt, What is Nodejs, for further understanding and information regarding how to use npm to install packages.

Create a simple server and a GET route

In our index.js file, we can add this code.

console.log("expense-tracker-simple-api");

// import the express lib
const express = require("express");

// create an express application
const app = express();

// create a GET request on the base endpoint
app.get("/", (req, res) => res.send("Hello world"));

// create a server that listens to requests on port 3000
app.listen(3000, () =>
  console.log(`Api running on ${"http://localhost:3000"}`)
);
Enter fullscreen mode Exit fullscreen mode

All that we did was to create an express application, use the application to create a GET request to send a message and let the application listen in on requests coming from port 3000.

const app = express();
Enter fullscreen mode Exit fullscreen mode

Creates an express application (🥳 that how to create an express application)

app.get("/", (req, res) => res.send("Hello world"));
Enter fullscreen mode Exit fullscreen mode

We used the express application instance to create a GET request. app has several methods and properties that include the HTTP methods. Check out the except about http methods here.

app.listen(3000, () =>
  console.log(`Api running on ${"http://localhost:3000"}`)
);
Enter fullscreen mode Exit fullscreen mode

We used the express application to listen on a port and used an arrow function to tell us, tell developers, that our application is running. For the port, we can alter it to another port of our choice. However, some special ports are already meant or used for some particular task and they are well known in the community and as such servers as default when such applications or programs are running on our PC. Check these out - 0 1 2

Note: There are some that are no-go because your system comes with them and there are some that come with applications we install such as some servers and databases and others. Fret not, when we use a port that is already in use, our application will let us know. All we have to do is add one or subtract one. No sweat.

Routes and Request handlers

To create a GET request, use app.get(...), for POST use app.post(...) and so on. For each route we want to create, the app.SomeMethod(...), will take a route or path, indicating which resource the user client desires or action they want to execute. As part of the route, it can take at least one request handler. This means we can have app.SomeMethod(path, hanlder1, handler2, ..., handlern);.

For the GET request above, the path or route is / (a string) and the single request handler we have is (req, res) => res.send("Hello world"), a handler function (a callback or a simple arrow function). The request handlers can be middleware and controllers.

Request and Response

A request handler normally takes in two arguments, the request and the response which are shortened as req and res respectively (you don't have to call them that but the first is always request and the second is the response). The request holds the data (or some information) about who made the request and what they want. The response is a means to provide feedback to the user who made the request. In this case, we send "Hello world" when the client makes a GET request to the / path.

Here you notice that the client and user are interchangeable, in the sense of which device makes the HTTP request to our api server and not a user, as in a user account.

Usually, the request handler will take a third argument which will point to the next handler after the previous handler has done its job. We call this next. It looks more or less like:

app.get("/", (req, res, next) => ...);
Enter fullscreen mode Exit fullscreen mode

The next argument is useful, it points to the request handler and at times it takes an argument, an error. We will implement an error handler to generally handle errors we did not handle or errors that we "pass" to the next request handler.

Now let's cancel the running nodejs process that was running (in the terminal), using control + c. Then run it again. This time with the updated content from Create a simple server and a GET route section, we should see an output in the console (terminal) similar to,

expense-tracker-simple-api
Api running on http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 in the browser. What do you see? A text saying, Hello world ?

Watch for changes

Rome was not built in one day as the saying goes. The same applies to software development. Maybe here what we mean is that we will gradually add more features as we develop and in this continuous process, it becomes irritating to start and stop the server all the time.

Go on, add another GET request (route) with /hello path and a request handler that says something you would want to say. Be happy.

You'd have to restart the server (the running nodejs process) and visit, http://localhost:3000/hello in the browser to see the response from that endpoint.

This,GET http://localhost:3000/hello, is an endpoint. You share this with api consumers. Among ourselves, we say route, because we don't have to know the whole URL (including protocol - http, domain - localhost, port - 3000, and path - /hello). Route is METHOD + PATH, more or less, GET /hello.

On macOS or Windows, we can do node --watch index.js or we can look out for changes not just in our entry file but in the whole folder path by, node --watch-path=./ index.js to watch for changes in the file path and also the file itself.

Currently, this is the content of my package.json file:

{
  "name": "expense-tracker-simple-api",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "express": "^4.21.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

We can add a script called dev under the script section.

"dev" : "node --watch-path=./ index.js"
Enter fullscreen mode Exit fullscreen mode

We can stop the running server with control + c and now execute npm run dev. This will monitor saved changes in our files and reload the server.

So if this is not working for you, we have an alternative. We will install nodemone, npm i nodemon -g. We'd use it everywhere as a utility tool so we don't have to install it as part of our packages. We can watch changes by executing, nodemon index.js. There are cases where this won't work and when it doesn't, dom nodemon --exec node index.js

We can modify our dev script to use nodemon by,

"dev" : "nodemon --exec node index.js"
Enter fullscreen mode Exit fullscreen mode

At this point, you can freely modify your .js files and on save, the server will restart to reload the load changes applied.

Create POST, GET, UPDATE, DELETE routes

We have already created a GET request. In this section, we will look into what each method means briefly since we have discussed them at length in Request and Response.

In this application, we are only serving one kind of resource to our clients, and that is expenditures. Assuming we are serving several resources, then we'd group requests under each resource.

So let's say we have user and expenditure, we have GET, POST, PUT, DELETE, etc for both users and expenditures.

For now, we'd use the /expenditures route to represent the expenditure resource.

  • GET: This means we will create a route to list, get all, fetch all, etc the records we have on expenditures. We can have a GET request that fetches one of the records. We have created a similar GET

  • POST: The post method is often used for creating resources

  • PUT: The put method is used to update recourses

  • DELETE: The delete method is used to delete resource

Now we can add the following lines of code to the index.js file but above app.listen(3000,...).

// --- expenditure routes ---

// list expenditures
app.get("/expenditures", (req, res) => res.send("list expenditures"));

// create expenditure
app.post("/expenditures", (req, res) => res.send("create expenditure"));

// update expenditure
app.put("/expenditures", (req, res) => res.send("update expenditure"));

// delete expenditure
app.delete("/expenditures", (req, res) => res.send("delete expenditure"));
Enter fullscreen mode Exit fullscreen mode

When you save your file, do you notice the logs in the terminal? Test the GET route for the expenditure in the browser.

We can only run the GET requests in the browser. We will discuss api clients next.

API Clients

An API client in this context is a tool, a program or an application that is used to interact (consume, integrate, or test) APIs. Most commonly used are Postman, Insomnia, curl, etc...

In vscode and alongside some other IDEs, there is an extension that provides extensions to api clients. vscode has some of these extensions for that matter. However, we will be considering an api client known as REST Client. For our use case, it will be simpler to use Rest Client as such don't fret. We are covered.

Note: postman or any other api client of your choice is okay to use

How to use REST Client

  • First, install, REST Client.
  • We are creating an HTTP request as such we can create a file with .http or .rest extension - touch expense-tracker-api.http
  • In expense-tracker-api.http we can define our request
  • To create a GET request, add the following to the .http file
  GET http://localhost:3000
Enter fullscreen mode Exit fullscreen mode
  • The endpoint is passed as seen above. For a post, put or delete a request to update the endpoint. Remember the difference between an endpoint and a route?
  • For a request that requires that data be passed to the api, we can pass the data as part of the route as a parameter or a string query, or we can pass it in the body.
  POST http://localhost:3000
  Content-Type: application/json

  {
      "property1": "value1",
      "property2": "value1",
      ...
      "propertyN": "valueN",
  }
Enter fullscreen mode Exit fullscreen mode
  • Content-Type: application/json is a header key-value. This means that's how you pass headers using rest-client.
  • For the request body, we pass it as a json object - a newline is expected between the headers and the body though
  • Each request can be separated by three pound or ash signs, ###. A text can be added at the end of ### to make it look as if it is a title.
  ### test base GET endpoint
  GET http://localhost:3000

  ### Create a dummy POST request
  POST http://localhost:3000
  Content-Type: application/json

  {
      "property1": "value1",
      "property2": "value1",
      ...
      "propertyN": "valueN",
  }
Enter fullscreen mode Exit fullscreen mode

As an exercise, create the request for the expenditure endpoints. Refer to when you are having any difficulties. We have more to do.

Request body, params, query, header

At this point, I don't have to stress that we'd be using JSON to communicate with our api using the API client.

As mentioned earlier, we can pass data to our api using the body, header, or URL of our request. We have seen how to pass data via the request body and the header (We will look into passing some specific data at another time). Check the POST request created. What we have not looked at is how to pass data as part of the URL.

Let's say we want to read an expenditure which has an id of 4, we can pass the add a parameter (as part of the URL) as, /expenditures/2. For the request which will be handling this requirement, we do, /expenditures/:id, where :id refers to the id of the expenditure. Assuming it is something else other than an id, let's say a name, then we'd do :name. Express will process this a provide us with a means to extract this value without sweat.

Now, for a query string, the idea is similar to request parameters however, it comes after a question, followed by a key1=value1&key2=value2...&keyN=valueN, where the key is the identifier of the value you want to pass. A very direct example is the REST-Client URL, https://marketplace.visualstudio.com/items?itemName=humao.rest-client. The question mark marks the beginning of the query string and whatever follows it maps a key to a value. Eg: ?itemName=humao.rest-client.

It will be a good time to test all your api endpoints and experience playing with it.

Request Body

Now we are going to process a request that passes data using the request body - The POST endpoint.

// create expenditure
app.post("/expenditures", (req, res) => res.send("create expenditure"));
Enter fullscreen mode Exit fullscreen mode

The request object has a property, body, and on this property, are the values that we passed in the request body of our request - req.body.

This is the request that will be running.

### Create Expenditures
POST http://localhost:3000/expenditures
Content-Type: application/json

{
    "name": "Legion Tower 7i Gen 8 (Intel) Gaming Desktop",
    "amount": 2099.99,
    "date": "2024-31-12"
}
Enter fullscreen mode Exit fullscreen mode

This is our endpoint implementation that will just log the request body to the console.

// create expenditure
app.post("/expenditures", (req, res) => {
  console.log(req.body);
  return res.send("create expenditure");
});
Enter fullscreen mode Exit fullscreen mode

What happened? We had the usual response but... undefined was logged in the console. Well, it means everything is alright however, our api server doesn't know that it should parse the incoming as json. Remember json?

Let's add this one line below the const app = express(); which should solve parse the incoming data as json.

// parse request body as json
app.use(express.json());
Enter fullscreen mode Exit fullscreen mode

Now, let's test the POST endpoint again. What did you get this time? Did you get something similar to this?

{
  "name": "Legion Tower 7i Gen 8 (Intel) Gaming Desktop",
  "amount": 2099.99,
  "date": "2024-31-12"
}
Enter fullscreen mode Exit fullscreen mode

Now you know how to get the data from the body. Now as exercise, destructure the body and pass an object in the response. So instead of logging it, return it as the response.

Request parameter

We will create a new GET endpoint to read an expenditure by id.

This will be our API request:

### Read Expenditure
GET http://localhost:3000/expenditures/1
Enter fullscreen mode Exit fullscreen mode

The request object has a property, params and on this property, are the values that we passed in the request param of our request - req.params.

Now the implementation will be similar to what we have done so far but a little different.

// read expenditure
app.get("/expenditures/:id", (req, res) => {
  /* const id = req.params.id;
  console.log(id) */
  console.log(req.params);
  return res.send("read expenditure");
});
Enter fullscreen mode Exit fullscreen mode

We can access the id directly too. I hope you noticed that the :id key or name passed as part of the route matches the key in the logged object. Try renaming the params key in the route and see the output logged.

Request query (query string)

For the query strings, there is a property on the request object, query, which exposes the query strings passed.

To demonstrate this will pass a query string to filter the records to return. This endpoint should do enough.

### List Expenditures
GET http://localhost:3000/expenditures?amountMoreThan=1000&nameStartsWith=Legion
Enter fullscreen mode Exit fullscreen mode

Now the implementation will be something similar to:

// list expenditures
app.get("/expenditures", (req, res) => {
  console.log(req.query);
  return res.send("list expenditures");
});
Enter fullscreen mode Exit fullscreen mode

Now run your api and check your logs. Experiment with this.

Manipulating memory data

At this point, we are not connecting to a database yet so we have to manipulate data from memory. What we intend to do is create an array, add to the array, update an element in it, and remove an element. It sounds feasible as such that's what we are going to do.

We will be doing some modifications to some previously written code as such, feel free to alter your too. The final excerpt will be shared at the end.

Initialize in-memory data

Let's create an array of expenditures (dummy data) below const express = require("express");

// dummy data
let expenditures = [
  {
    name: "Legion Tower 7i Gen 8 (Intel) Gaming Desktop",
    amount: 2099.99,
    date: "2024-12-31",
  },
  {
    name: "Apple MacBook Pro 16-inch",
    amount: 2499.99,
    date: "2024-12-15",
  },
  {
    name: "Samsung Galaxy S24 Ultra",
    amount: 1199.99,
    date: "2024-12-10",
  },
  {
    name: "Sony WH-1000XM5 Noise Cancelling Headphones",
    amount: 349.99,
    date: "2024-12-20",
  },
  {
    name: "Dell UltraSharp 27 4K USB-C Monitor",
    amount: 699.99,
    date: "2024-11-25",
  },
  {
    name: "Logitech MX Keys Wireless Keyboard",
    amount: 119.99,
    date: "2024-10-05",
  },
  {
    name: "HP Envy x360 Convertible Laptop",
    amount: 1349.99,
    date: "2024-09-30",
  },
  {
    name: "NVIDIA GeForce RTX 4080 Graphics Card",
    amount: 1199.99,
    date: "2024-08-15",
  },
  {
    name: "ASUS ROG Zephyrus G14 Gaming Laptop",
    amount: 1599.99,
    date: "2024-07-18",
  },
  {
    name: "Canon EOS R6 Mark II Mirrorless Camera",
    amount: 2499.99,
    date: "2024-07-12",
  },
];
Enter fullscreen mode Exit fullscreen mode

List expenditures

The current endpoint returns just a message using the res.send(message) but what we want to return is json. Though the .send can also take in an object or json, we will use res.json(obj).

I didn't mention but the default status code returned is 200. Have you noticed that? Except that another occurs or there is an issue with the request, the status code remains the same. There is a section under status codes, glance through.

We can alter the status code by passing the desired status code in res.status(desireStatusCode).json(obj). I will maintain the 200 status code throughout.

Make sure the server is still running

We can pass the list of expenditures directly.

// list expenditures
app.get("/expenditures", (req, res) => {
  return res.json(expenditures);
});
Enter fullscreen mode Exit fullscreen mode

What was the response received? Check the status code as well as the response payload.

From experience and also to avoid ambiguity, I prefer to return status code 200 by default and have a either success property, message or data property to return a message or requested resource. By default, when status is false, message will be passed else, message or data may be passed.

// list expenditures
app.get("/expenditures", (req, res) => {
  return res.json({
    success: true,
    data: expenditures,
  });
});
Enter fullscreen mode Exit fullscreen mode

We need to display the id (index of each row)

// list expenditures
app.get("/expenditures", (req, res) => {
  return res.json({
    success: true,
    data: expenditures.map((row, index) => ({ id: index, ...row })),
  });
});
Enter fullscreen mode Exit fullscreen mode

Apply filtering with

// list expenditures
app.get("/expenditures", (req, res) => {
  // get the query string and check if it is not a number or something
  // that can be a number else set a default filter value of 0
  let amountMoreThan = Number(req.query.amountMoreThan);
  if (isNaN(amountMoreThan) || amountMoreThan < 0) {
    amountMoreThan = 0;
  }

  return res.json({
    success: true,
    data: expenditures
      .map((row, index) => ({ id: index, ...row }))
      .filter((row) => row.amount > amountMoreThan),
  });
});
Enter fullscreen mode Exit fullscreen mode

Why was the filter done after the mapping?

Read expenditure

// read expenditure
app.get("/expenditures/:id", (req, res) => {
  // get the id of the request resource from the params
  const id = Number(req.params.id);
  if (isNaN(id) || id < 0) {
    return res.status(200).json({
      success: false,
      message: "Resource ID invalid",
    });
  }

  // we don't take negative ids, why?
  const row = expenditures[id];
  if (!row) {
    // what status code do this should be passed here? 404 not not found? why??
    return res.status(200).json({
      success: false,
      message: `Resource with ID, '${id}' not found`,
    });
  }

  return res.status(200).json({
    success: true,
    data: row,
  });
});
Enter fullscreen mode Exit fullscreen mode

Does this implementation hint to you about, Why was the filter done after the mapping? ?

Create expenditure

// create expenditure
app.post("/expenditures", (req, res) => {
  // const { name, amount, date } = req.body
  const payload = req.body;

  if (!payload?.name || !payload.amount || !payload.date) {
    return res.status(200).json({
      success: false,
      message: "name, amount and date for expense are required",
    });
  }

  // we have to validate the name, maybe, their name must have some number of characters
  // we have to make sure that amount is a number
  // we have to also make sure that the date is of the format, yyyy-MM-dd

  // insert the new record
  expenditures.push({
    name: payload.name,
    amount: payload.amount,
    date: payload.date,
  });

  return res.status(201).json({
    success: true,
    message: "expenditure created successfully",
  });

  // there situations that it is best that you pass the new record created
  /* return res.status(201).json({
    success: true,
    message: "expenditure created successfully",
    data: expenditures[expenditures.length - 1],
  }); */

  // pass a route to fetch the new record created
  /* return res.status(201).json({
    success: true,
    message: "expenditure created successfully",
    route: `/expenditures/${expenditures[expenditures.length - 1]}`,
  }); */
});
Enter fullscreen mode Exit fullscreen mode

Update expenditure

// update expenditure
app.put("/expenditures/:id", (req, res) => {
  // get the id of the request resource from the params
  const id = Number(req.params.id);
  if (isNaN(id) || id < 0) {
    return res.status(200).json({
      success: false,
      message: "Resource ID invalid",
    });
  }

  // we don't take negative ids, why?
  const row = expenditures[id];
  if (!row) {
    // what status code do this should be passed here? 404 not not found? why??
    return res.status(200).json({
      success: false,
      message: `Resource with ID, '${id}' not found`,
    });
  }

  // we have to validate the name, maybe, their name must have some number of characters
  // we have to make sure that amount is a number
  // we have to also make sure that the date is of the format, yyyy-MM-dd
  const { name, amount, date } = req.body;

  expenditures[id].name = name ?? row.name;
  expenditures[id].amount = amount ?? row.amount;
  expenditures[id].date = date ?? row.date;

  // there was a time I saw a 204 status - No content
  // since there was no data to return however we'll return the usual
  return res.status(200).json({
    success: true,
    message: "expenditure updated successfully",
  });
});
Enter fullscreen mode Exit fullscreen mode

Delete expenditure

// delete expenditure
app.delete("/expenditures/:id", (req, res) => {
  // get the id of the request resource from the params
  const id = Number(req.params.id);
  if (isNaN(id) || id < 0) {
    return res.status(200).json({
      success: false,
      message: "Resource ID invalid",
    });
  }

  // we don't take negative ids, why?
  const row = expenditures[id];
  if (!row) {
    // what status code do this should be passed here? 404 not not found? why??
    return res.status(200).json({
      success: false,
      message: `Resource with ID, '${id}' not found`,
    });
  }

  expenditures = expenditures.filter((_, index) => index !== id);

  return res.status(200).json({
    success: true,
    message: "expenditure deleted successfully",
  });
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

We have covered the root of most API developments. This project is as basic as it comes. Relax and glance through again. There is more to look into such as

  • validation
  • authentication and authorization
  • middleware
  • error handling
  • SQL
  • database integration

Practice project

crud api = create, list, read, update, and delete. It is how you approach these problems.

To-Do List

  • todo object: { id: int, task: string, status: boolean }
  • crud api
  • add an endpoint to mark all tasks as completed, success is true or not completed

Calculator⁠

  • you have to decide, whether you'd create an endpoint for all the operations (addition, subtraction, multiplication, division)
  • or you would create a single endpoint with different functions that correspond to each operation. The user should be able to pass the operator and the two operands

Currency Converter⁠
You are converting from one currency to another. Do for as many currencies as you can (3 is enough)

  • Unit Converter⁠ ⁠- Notes App⁠ ⁠- Personal Blog⁠ ⁠- Quiz App⁠

Snippets

Know that the excess was removed.

// import the express lib
const express = require("express");

// dummy data
let expenditures = [
  {
    name: "Legion Tower 7i Gen 8 (Intel) Gaming Desktop",
    amount: 2099.99,
    date: "2024-12-31",
  },
  {
    name: "Apple MacBook Pro 16-inch",
    amount: 2499.99,
    date: "2024-12-15",
  },
  {
    name: "Samsung Galaxy S24 Ultra",
    amount: 1199.99,
    date: "2024-12-10",
  },
];

// create an express application
const app = express();

// parse request body as json
app.use(express.json());

// list expenditures
app.get("/expenditures", (req, res) => {
  // get the query string and check if it is not a number or something
  // that can be a number else set a default filter value of 0
  let amountMoreThan = Number(req.query.amountMoreThan);
  if (isNaN(amountMoreThan) || amountMoreThan < 0) {
    amountMoreThan = 0;
  }

  return res.json({
    success: true,
    data: expenditures
      .map((row, index) => ({ id: index, ...row }))
      .filter((row) => row.amount > amountMoreThan),
  });
});

// read expenditure
app.get("/expenditures/:id", (req, res) => {
  // get the id of the request resource from the params
  const id = Number(req.params.id);
  if (isNaN(id) || id < 0) {
    return res.status(200).json({
      success: false,
      message: "Resource ID invalid",
    });
  }

  // we don't take negative ids, why?
  const row = expenditures[id];
  if (!row) {
    // what status code do this should be passed here? 404 not not found? why??
    return res.status(200).json({
      success: false,
      message: `Resource with ID, '${id}' not found`,
    });
  }

  return res.status(200).json({
    success: true,
    data: row,
  });
});

// create expenditure
app.post("/expenditures", (req, res) => {
  // const { name, amount, date } = req.body
  const payload = req.body;

  if (!payload?.name || !payload.amount || !payload.date) {
    return res.status(200).json({
      success: false,
      message: "name, amount and date for expense are required",
    });
  }

  // we have to validate the name, maybe, their name must have some number of characters
  // we have to make sure that amount is a number
  // we have to also make sure that the date is of the format, yyyy-MM-dd

  // insert the new record
  expenditures.push({
    name: payload.name,
    amount: payload.amount,
    date: payload.date,
  });

  return res.status(201).json({
    success: true,
    message: "expenditure created successfully",
  });

  // there situations that it is best that you pass the new record created
  /* return res.status(201).json({
    success: true,
    message: "expenditure created successfully",
    data: expenditures[expenditures.length - 1],
  }); */

  // pass a route to fetch the new record created
  /* return res.status(201).json({
    success: true,
    message: "expenditure created successfully",
    route: `/expenditures/${expenditures[expenditures.length - 1]}`,
  }); */
});

// update expenditure
app.put("/expenditures/:id", (req, res) => {
  // get the id of the request resource from the params
  const id = Number(req.params.id);
  if (isNaN(id) || id < 0) {
    return res.status(200).json({
      success: false,
      message: "Resource ID invalid",
    });
  }

  // we don't take negative ids, why?
  const row = expenditures[id];
  if (!row) {
    // what status code do this should be passed here? 404 not not found? why??
    return res.status(200).json({
      success: false,
      message: `Resource with ID, '${id}' not found`,
    });
  }

  // we have to validate the name, maybe, their name must have some number of characters
  // we have to make sure that amount is a number
  // we have to also make sure that the date is of the format, yyyy-MM-dd
  const { name, amount, date } = req.body;

  expenditures[id].name = name ?? row.name;
  expenditures[id].amount = amount ?? row.amount;
  expenditures[id].date = date ?? row.date;

  // there was a time I saw a 204 status - No content
  // since there was no data to return however we'll return the usual
  return res.status(200).json({
    success: true,
    message: "expenditure updated successfully",
  });
});

// delete expenditure
app.delete("/expenditures/:id", (req, res) => {
  // get the id of the request resource from the params
  const id = Number(req.params.id);
  if (isNaN(id) || id < 0) {
    return res.status(200).json({
      success: false,
      message: "Resource ID invalid",
    });
  }

  // we don't take negative ids, why?
  const row = expenditures[id];
  if (!row) {
    // what status code do this should be passed here? 404 not not found? why??
    return res.status(200).json({
      success: false,
      message: `Resource with ID, '${id}' not found`,
    });
  }

  expenditures = expenditures.filter((_, index) => index !== id);

  return res.status(200).json({
    success: true,
    message: "expenditure deleted successfully",
  });
});

// create a server that listens to requests on port 3000
app.listen(3000, () =>
  console.log(`Api running on ${"http://localhost:3000"}`)
);
Enter fullscreen mode Exit fullscreen mode

API requests

# Expenditure endpoints

### Create Expenditures
POST http://localhost:3000/expenditures
Content-Type: application/json

{
    "name": "Legion Tower 7i Gen 8 (Intel) Gaming Desktop",
    "amount": 2099.99,
    "date": "2024-31-12"
}

### List Expenditures
GET http://localhost:3000/expenditures?

### Read Expenditure
GET http://localhost:3000/expenditures/1

### Update Expenditure
PUT http://localhost:3000/expenditures/11
Content-Type: application/json

{
    "name": "MacBook pro laptop",
    "amount": 5099.99,
    "date": "2025-01-01"
}

### Delete Expenditure
DELETE http://localhost:3000/expenditures/0
Enter fullscreen mode Exit fullscreen mode

Top comments (0)