diff --git a/README.md b/README.md index 5241826b3..326f4c5b1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,76 @@ # Project Express API -Replace this readme with your own information about your project. +This project marks the beginning of my backend journey with Express.js, featuring RESTful endpoints that return data from a hard-coded JSON file. -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +Array methods like .find(), .filter(), and .slice() are used to manipulate the data. While pre-made datasets are available, I chose to create my own for a personalized touch. -## The problem +My crew of elves, the Backend Dashers, can be found at /elves/title/backend%20dasher or at /elves/top-twelves -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +## Requirements: + 1. The API should have at least 3 routes. + This API has the following routes: + - /: Returns documentation of the API using express-list-endpoints.[Express List Endpoints](https://www.npmjs.com/package/express-list-endpoints). + - /elves/all - Get all elves + - /elves/top-twelves - Get top TwElves using .slice() + - /elves/:id: - Get a specific elf by ID using .find() + - /elves/titles/:title: - Get elves by title using .filter() + - /test - Test endpoint + 2. A minimum of one endpoint to return a **collection** of results (array of elements). + - elves/all: Returns a collection of elves. + - /elves/top-twelves - Get top TwElves. + - /elves/titles/:title: Returns elves by their title. + 3. A minimum of one endpoint to return a **single** result (single element). + - /elves/:id: Returns a single elf by ID. + 4. Your API should be RESTful. + - Routes (elves) are defined with endpoints such as /elves/:id, and /elves/titles/:title. + - HTTP methods are used (GET). + - Responses are structured in JSON format. + 5. You should follow the guidelines on how to write clean code. + - Variable names are clear and descriptive (e.g. request, response, title). + - Each functionality is separated into its own endpoint. + - express.json() and cors() to handle JSON parsing and CORS issues + +## Dependency Installation & Startup Development Server +This project uses npm (Node Package Manager) and Express.js to manage dependencies and run the development server. Follow these steps to get started: + 1. Install Project Dependencies + Run the following commands to install necessary packages and set up the development environment: + ```bash + npm install + npm run dev + npm run build + ``` + 2. If Express.js is not already installed, initialize your project and install it: + ```bash + npm init -y + npm install express + ``` + 3. Start your server + Launch the server: + ```bash + node server.js + ``` + 4. The package used to generate a list of all available API endpoints automatically (shown on the endpoint /). Install it with: + ```bash + npm install express-list-endpoints + ``` + +## The problem +This was my first backend project, and I found it a bit confusing to figure out what to install and why. I’m also not used to working without a UI, which made it tricky to test everything properly without clickable links or visual "feedback". + +## If I had more time - Stretch goals + +### Intermediate Stretch Goals +- On routes which return a single item, handle when the item doesn't exist and return some useful data in the response. + +- Accept filters via query parameters to filter the data you return from endpoints which return an array of data. + +- Create some empty/dummy endpoints which could contain more complex operations in the future. Find good names for them (think RESTful). + +### Advanced Stretch Goals +- Build a frontend which uses your API in some way to show the data in a nice way (use Vite to get up and running fast). + +- If your dataset is large, try implementing 'pages' using `.slice()` to return only a selection of results from the array. You could then use a query parameter to allow the client to ask for the next 'page'. ## View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +[Check out the Elf's API here!](https://project-express-api-gyq9.onrender.com/) diff --git a/data/elves.json b/data/elves.json new file mode 100644 index 000000000..4ef35459a --- /dev/null +++ b/data/elves.json @@ -0,0 +1,352 @@ +[ + { + "elfID": 1, + "title": "Backend Dasher", + "Name": "Joyce", + "language_code": ["en", "js"], + "reviews_count": 10 + }, + { + "elfID": 2, + "title": "Backend Dasher", + "Name": "Anna", + "language_code": ["en", "js"], + "reviews_count": 14 + }, + { + "elfID": 3, + "title": "Backend Dasher", + "Name": "Erika", + "language_code": ["en", "js"], + "reviews_count": 18 + }, + { + "elfID": 4, + "title": "Backend Dasher", + "Name": "Helene", + "language_code": ["en", "js"], + "reviews_count": 16 + }, + { + "elfID": 5, + "title": "Backend Dasher", + "Name": "Fanny", + "language_code": ["en", "js"], + "reviews_count": 20 + }, + { + "elfID": 6, + "title": "Backend Dasher", + "Name": "Johanna", + "language_code": ["en", "js"], + "reviews_count": 19 + }, + { + "elfID": 7, + "title": "Backend Dasher", + "Name": "Gitte", + "languages": ["en", "de", "js"], + "reviews_count": 12 + }, + { + "elfID": 8, + "title": "Backend Dasher", + "Name": "Gabriella", + "language_code": ["en", "js"], + "reviews_count": 7 + }, + { + "elfID": 9, + "title": "Backend Dasher", + "Name": "Kelly", + "language_code": ["en", "js"], + "reviews_count": 13 + }, + { + "elfID": 10, + "title": "Backend Dasher", + "Name": "Xing", + "language_code": ["en", "js"], + "reviews_count": 6 + }, + { + "elfID": 11, + "title": "Backend Dasher", + "Name": "Zoe", + "language_code": ["en", "js"], + "reviews_count": 10 + }, + { + "elfID": 12, + "title": "Backend Dasher", + "Name": "Liselotte", + "language_code": ["en", "js"], + "reviews_count": 5 + }, + { + "elfID": 13, + "title": "Toy Designer", + "Name": "Buddy", + "language_code": "en", + "reviews_count": 5 + }, + { + "elfID": 14, + "title": "Gift Wrapper", + "Name": "Olof", + "language_code": "en", + "reviews_count": 24 + }, + { + "elfID": 15, + "title": "Cookie Tester", + "Name": "Karl", + "language_code": "en", + "reviews_count": 21 + }, + { + "elfID": 16, + "title": "Cookie Tester", + "Name": "Elsa", + "languages": ["en", "de"], + "reviews_count": 21 + }, + { + "elfID": 17, + "title": "Gift Wrapper", + "Name": "Olivia", + "language_code": "en", + "reviews_count": 10 + }, + { + "elfID": 18, + "title": "Sleigh Mechanic", + "Name": "Liam", + "language_code": "en", + "reviews_count": 20 + }, + { + "elfID": 19, + "title": "Toy Designer", + "Name": "Sophia", + "languages": ["en", "de"], + "reviews_count": 9 + }, + { + "elfID": 20, + "title": "Gift Wrapper", + "Name": "Zustand", + "languages": ["en", "de", "js"], + "reviews_count": 17 + }, + { + "elfID": 21, + "title": "Gift Wrapper", + "Name": "Mia", + "language_code": "en", + "reviews_count": 29 + }, + { + "elfID": 22, + "title": "Cookie Tester", + "Name": "Ethan", + "language_code": "en", + "reviews_count": 27 + }, + { + "elfID": 23, + "title": "Cookie Tester", + "Name": "Emily", + "language_code": "en", + "reviews_count": 14 + }, + { + "elfID": 24, + "title": "Sleigh Mechanic", + "Name": "Logan", + "language_code": "en", + "reviews_count": 12 + }, + { + "elfID": 25, + "title": "Cookie Tester", + "Name": "Ella", + "language_code": "en", + "reviews_count": 18 + }, + { + "elfID": 26, + "title": "Sleigh Mechanic", + "Name": "Lucas", + "language_code": "en", + "reviews_count": 8 + }, + { + "elfID": 27, + "title": "Sleigh Mechanic", + "Name": "Ava", + "language_code": "en", + "reviews_count": 7 + }, + { + "elfID": 28, + "title": "Cookie Tester", + "Name": "Mason", + "language_code": "en", + "reviews_count": 24 + }, + { + "elfID": 29, + "title": "Cookie Tester", + "Name": "Isabella", + "language_code": "en", + "reviews_count": 26 + }, + { + "elfID": 30, + "title": "Toy Designer", + "Name": "Jacob", + "language_code": "en", + "reviews_count": 22 + }, + { + "elfID": 31, + "title": "Reindeer Trainer", + "Name": "Harper", + "language_code": "en", + "reviews_count": 6 + }, + { + "elfID": 32, + "title": "Sleigh Mechanic", + "Name": "Elijah", + "language_code": "en", + "reviews_count": 28 + }, + { + "elfID": 33, + "title": "Reindeer Trainer", + "Name": "Charlotte", + "language_code": "en", + "reviews_count": 6 + }, + { + "elfID": 34, + "title": "Sleigh Mechanic", + "Name": "Alexander", + "language_code": "en", + "reviews_count": 12 + }, + { + "elfID": 35, + "title": "Sleigh Mechanic", + "Name": "Amelia", + "language_code": "en", + "reviews_count": 18 + }, + { + "elfID": 36, + "title": "Gift Wrapper", + "Name": "James", + "language_code": "en", + "reviews_count": 22 + }, + { + "elfID": 37, + "title": "Toy Designer", + "Name": "Abigail", + "language_code": "en", + "reviews_count": 28 + }, + { + "elfID": 38, + "title": "Sleigh Mechanic", + "Name": "Benjamin", + "language_code": "en", + "reviews_count": 24 + }, + { + "elfID": 39, + "title": "Sleigh Mechanic", + "Name": "Sofia", + "language_code": "en", + "reviews_count": 10 + }, + { + "elfID": 40, + "title": "Sleigh Mechanic", + "Name": "William", + "language_code": "en", + "reviews_count": 18 + }, + { + "elfID": 41, + "title": "Reindeer Trainer", + "Name": "Emma", + "language_code": "en", + "reviews_count": 28 + }, + { + "elfID": 42, + "title": "Reindeer Trainer", + "Name": "Oliver", + "language_code": "en", + "reviews_count": 19 + }, + { + "elfID": 43, + "title": "Gift Wrapper", + "Name": "Aria", + "language_code": "en", + "reviews_count": 26 + }, + { + "elfID": 44, + "title": "Toy Designer", + "Name": "Henry", + "language_code": "en", + "reviews_count": 22 + }, + { + "elfID": 45, + "title": "Toy Designer", + "Name": "Scarlett", + "language_code": "en", + "reviews_count": 25 + }, + { + "elfID": 46, + "title": "Gift Wrapper", + "Name": "Samuel", + "language_code": "en", + "reviews_count": 10 + }, + { + "elfID": 47, + "title": "Reindeer Trainer", + "Name": "Hannah", + "language_code": "en", + "reviews_count": 26 + }, + { + "elfID": 48, + "title": "Cookie Tester", + "Name": "Jack", + "language_code": "en", + "reviews_count": 7 + }, + { + "elfID": 49, + "title": "Toy Designer", + "Name": "Grace", + "language_code": "en", + "reviews_count": 5 + }, + { + "elfID": 50, + "title": "Toy Designer", + "Name": "Levi", + "language_code": "en", + "reviews_count": 15 + } +] \ No newline at end of file diff --git a/package.json b/package.json index f93ddb524..6d061754c 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,22 @@ "name": "project-express-api", "version": "1.0.0", "description": "Starter project to get up and running with express quickly", + "type": "commonjs", "scripts": { "start": "babel-node server.js", "dev": "nodemon server.js --exec babel-node" }, - "author": "", + "author": "Johanna Eriksson", "license": "ISC", "dependencies": { "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", - "express": "^4.17.3", + "express": "^4.21.1", + "express-list-endpoints": "^7.1.1", "nodemon": "^3.0.1" - } + }, + "main": "server.js", + "keywords": [] } diff --git a/server.js b/server.js index b5fec6fe2..3a5918055 100644 --- a/server.js +++ b/server.js @@ -1,30 +1,97 @@ import express from "express"; import cors from "cors"; +import expressListEndpoints from 'express-list-endpoints'; +import elves from "./data/elves.json"; -// If you're using one of our datasets, uncomment the appropriate import below -// to get started! -// import avocadoSalesData from "./data/avocado-sales.json"; -// import booksData from "./data/books.json"; -// import goldenGlobesData from "./data/golden-globes.json"; -// import netflixData from "./data/netflix-titles.json"; -// import topMusicData from "./data/top-music.json"; - -// Defines the port the app will run on. Defaults to 8080, but can be overridden -// when starting the server. Example command to overwrite PORT env variable value: -// PORT=9000 npm start -const port = process.env.PORT || 8080; +// Defines the port the app will run on. Defaults to 8080, but can be overridden when starting the server. Example command to overwrite PORT env variable value: PORT=1224 npm start +const port = process.env.PORT || 1224; // Hoho const app = express(); // Add middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); -// Start defining your routes here -app.get("/", (req, res) => { - res.send("Hello Technigo!"); +// Documentation endpoint +app.get("/", (request, response) => { + const endpoints = expressListEndpoints(app); + response.json({ + message: "Welcome to the Elves API! Here are the available endpoints:", + description: { + "/elves/all": "Get all elves", + "/elves/top-twelves": "Get the top twelves", + "/elves/titles/:title": "Get elves by title", + "/elves/:id": "Get a specific elf by ID", + "/test": "Test endpoint", + endpoints: endpoints + } + }); }); -// Start the server +/** + * Endpoint for getting all elves. + * This endpoint returns the complete list of elves from the elves.json. + */ + +app.get("/elves/all", (request, response) => { + + // Return all elves + response.json(elves); +}); + +/** + * Endpoint to get the top 12 elves, the "TwElves" + * This endpoint uses .slice() to return the first 12 elves from the elves.json. + */ +app.get("/elves/top-twelves", (request, response) => { + const topElves = elves.slice(0, 12); + + // Return top 12 elves + response.json(topElves); +}); + +/** + * Endpoint for getting elves based on the provided title. + * This endpoint uses .filter() to return the elves with a matching title + */ +app.get("/elves/titles/:title", (request, response) => { + const title = request.params.title.toLowerCase(); + const filteredElves = elves.filter((elf) => elf.title.toLowerCase() === title); + + // Return elves with titles that match + response.json(filteredElves); +}); + +/** + * Endpoint for getting elves based on a unique ID. + * This endpoint uses .find() to search for the elf in the elves.json. + * If an elf with the given ID exists, it returns the elf's data with a 200 status. + * If no elf is found, it returns with a 404 status and the message: "404 - No elf found with that ID". + */ + +app.get("/elves/:id", (request, response) => { + const id = request.params.id; + + const elf = elves.find((record) => record.elfID === +id); + if (elf) { + response.status(200).json(elf); + } else { + response.status(404).send("404 - No elf found with that ID"); + } +}) + +/** + * Endpoint for testing the server. + * This endpoint confirms that the server is running and responds with "Jingle bells, the server tells, it's up and running well!" + */ +app.get("/test", (request, response) => { + response.send("Jingle bells, the server tells, it's up and running well!"); + console.log("Jingle bells, the server tells, it's up and running well!"); +}); + +/** + * Start the server. + * The server listens on the specified port and logs the URL to the console. + */ app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); });