diff --git a/README.md b/README.md index 6a75d8e1..000d0150 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,33 @@ # Project Happy Thoughts API -Replace this readme with your own information about your project. +This is the backend API for the Happy Thoughts project, used to store and manage "happy thoughts" submitted by users. It is built with Node.js, Express, and MongoDB, hosted on Render. -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +### Features +- **GET /thoughts**: Fetches the 20 most recent happy thoughts, sorted by creation date. +- **POST /thoughts**: Allows users to submit a new happy thought. +- **POST /thoughts/:thoughtId/like**: Adds a like (heart) to a specific thought by ID. + +### Deployed Backend +- **Base URL**: [https://project-happy-thoughts-api-gns9.onrender.com](https://project-happy-thoughts-api-gns9.onrender.com) + +### Frontend Integration +This backend is connected to the [Happy Thoughts Frontend](https://github.com/joheri1/project-happy-thoughts-vite), a React application for displaying and interacting with the happy thoughts. + +- **Backend Repository**: [Happy Thoughts API](https://github.com/joheri1/project-happy-thoughts-api) +- **Backend URL**: [https://project-happy-thoughts-api-gns9.onrender.com](https://project-happy-thoughts-api-gns9.onrender.com) ## The problem +### Connecting the Frontend, Backend, and Database +When I started integrating my React frontend, Node.js backend, and MongoDB Atlas database, I faced several issues. Here's a few of them: +- My MongoDB Atlas user seemed to have the correct password, but for some non-obvious reason, the connection still failed. I resolved this by generating a new password for the user and updating the `MONGO_URL` in Renderโ€™s environment variables. +- During development, I got an error saying that `savedThought` was not defined in the `POST /thoughts` endpoint. This was caused by a typo, and I fixed it by correctly referencing the newly created thought object as `newThought`. +- The error 'Operation 'happythoughts.find()' buffering timed out' because the database wasnโ€™t properly configured in MongoDB Atlas. This was resolved by adding 0.0.0.0/0 in Network Access in MongoDB Atlas to allow connections from any IP address (necessary for Render). + +I used Postman a lot to test my API endpoints and MongoDB Compass to confirm that data was being stored in the database. I uploaded a json with a few "happy thoughts" to make the testing easier, while debugging issues with my initial POST request. -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? +I also sketched a simple flow on paper to map out how each part of the application interacts, such as how MONGO_URL is used in the backend to connect to MongoDB Atlas, and that BASE_URL links the frontend to the backend. This really helped when debugging. ## 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. +- **Frontend URL**: [Project Happy Thoughts](https://project-happy-thoughts-x.netlify.app/) +- **Backend URL**: [https://project-happy-thoughts-api-gns9.onrender.com](https://project-happy-thoughts-api-gns9.onrender.com) diff --git a/models/MongooseModel b/models/MongooseModel new file mode 100644 index 00000000..fbb562d4 --- /dev/null +++ b/models/MongooseModel @@ -0,0 +1,26 @@ +// Purpose: Define the Mongoose model for the HappyThoughts collection in the MongoDB database +// Dependencies: mongoose + +import mongoose from 'mongoose'; + +const happyThoughtSchema = new mongoose.Schema({ + message: { + type: String, + required: [true,'A message is required'], + minlength: [5, 'Message must be at least 5 characters'], + maxlength: [140, 'Message must be at most 140 characters'] + }, + hearts: { + type: Number, + default: 0 + }, + createdAt: { + type: Date, + default: () => new Date(), + immutable: true + } +}); + +const HappyThought = mongoose.model('HappyThought', happyThoughtSchema); + +export default HappyThought; diff --git a/package.json b/package.json index 1c371b45..07f72bfd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", "express": "^4.17.3", + "express-list-endpoints": "^7.1.1", + "mongodb": "^6.12.0", "mongoose": "^8.0.0", - "nodemon": "^3.0.1" + "nodemon": "^3.0.1", + "dotenv": "^16.0.0" } -} \ No newline at end of file +} diff --git a/routes/happyThought.js b/routes/happyThought.js new file mode 100644 index 00000000..b803e96e --- /dev/null +++ b/routes/happyThought.js @@ -0,0 +1,72 @@ +/** This file contains all the routes */ + +import HappyThought from "../models/MongooseModel"; +import express from "express"; +import expressListEndpoints from "express-list-endpoints"; + +const app = express.Router(); + +/** + * Documentation for the API. + */ +app.get("/", (request, response) => { + const endpoints = expressListEndpoints(app); // Get all endpoints + response.json({ + message: "Welcome to the Happy Thoughts API ๐ŸŒŸ", + endpoints: endpoints, + }); +}); + +/** + * Endpoint to GET 20 thoughts. +*/ +app.get("/thoughts", async (request, response) => { + try { + const thoughts = await HappyThought.find() + .sort({ createdAt: "desc" }) + .limit(20); + response.status(200).json(thoughts); + } catch (error) { + response.status(400).json({ message: "Could not get thoughts", error: error.message }); + } +}); + +/** + * Endpoint to POST a thought. + */ +app.post("/thoughts", async (request, response) => { + const { message } = request.body; + + try { + const newThought = await HappyThought.create({ message }); + response.status(201).json(newThought); + } catch (error) { + response.status(400).json({ message: "Could not save thought to the Database", error: error.message }); + } +}); + +/** + * Endpoint to POST a like. + */ + +app.post("/thoughts/:thoughtId/like", async (request, response) => { + const { thoughtId } = request.params; + + try { + const updatedThought = await HappyThought.findByIdAndUpdate(thoughtId, + { $inc: { hearts: 1 } }, // Add 1 heart/like to the thought + { new: true } // Return the updated thought + ); + + if (!updatedThought) { + return response.status(404).json({ message: "Thought not found" }); + } + + response.status(200).json(updatedThought); + + } catch (error) { + response.status(400).json({ message: "Could not like thought", error: error.message }); + } +}); + +export default app; diff --git a/server.js b/server.js index dfe86fb8..0d76a824 100644 --- a/server.js +++ b/server.js @@ -1,14 +1,23 @@ import cors from "cors"; import express from "express"; import mongoose from "mongoose"; +import routes from "./routes/happyThought"; + +import dotenv from "dotenv"; +dotenv.config(); + +// Connects to the Mongo database +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-happy-thoughts-api"; + +console.log("MongoDB URL:", mongoUrl); + +mongoose.connect(mongoUrl) + .then(() => console.log("Connected to the database ๐Ÿš€")) + .catch((error) => console.error("Could not connect to the database", error)); -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; -mongoose.connect(mongoUrl); mongoose.Promise = Promise; -// 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 +// Defines the port the app will run on. const port = process.env.PORT || 8080; const app = express(); @@ -16,12 +25,20 @@ const app = express(); app.use(cors()); app.use(express.json()); -// Start defining your routes here -app.get("/", (req, res) => { - res.send("Hello Technigo!"); +//Use routes fot HappyThoughts +app.use("/", routes); + +/** + * Endpoint for testing the server. + */ +app.get("/test", (request, response) => { + response.send("The server is up, so come share your happy thoughts with us! ๐ŸŒŸ"); + console.log("The server is up, so come share your happy thoughts with us! ๐ŸŒŸ"); }); -// Start the server +/** + * Start the server. + */ app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`); + console.log(`Server is spreading joy on http://localhost:${port} ๐ŸŽ‰`); });