Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xing - project-happy-thoughts-api #499

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
# Project Happy Thoughts API

Replace this readme with your own information about your project.
This project centers around creating a fully functional Happy Thoughts Messaging API using Node.js, Express, and MongoDB. The API integrates seamlessly with the Happy Thoughts React application, enabling users to fetch existing thoughts and post new ones through clearly defined endpoints.

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
## `Highlights`
- `RESTful Endpoints`:
Developed GET endpoints to retrieve a collection of recently created happy thoughts—limited to 20 results—and POST endpoints to add new thoughts or increment the "heart" count of existing ones.
- `Data Validation & Error Handling`:
Implemented robust input validation to ensure that all submissions meet the defined criteria. Returned meaningful error messages and set the appropriate HTTP status codes (notably 400 Bad Request) for invalid inputs, and 404 Not Found when modifying non-existent thoughts.
- `Clean Code & Scalability`:
Followed industry best practices for clarity, maintainability, and scalability. Utilized MongoDB and Mongoose to store and retrieve data, ensuring a stable and flexible database structure.

## The problem

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?
## `Technologies & Tools`
- `Node.js & Express`: Formed the backbone of the backend logic, efficiently handling routing and server management.
- `MongoDB & Mongoose`: Provided a document-based database solution, simplifying the process of storing, querying, and updating message entries.
- `Testing & Validation`: Leveraged Postman (or similar tools) to test and verify endpoints, ensuring the API behaved as intended and responded correctly under various scenarios.

## 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.
https://project-happy-thoughts-api-h0r6.onrender.com/
27 changes: 27 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import express from "express";
import cors from "cors";
import bodyParser from "body-parser";
import expressListEndpoints from "express-list-endpoints";

import { errorHandler } from "./middleware/errorHandler.js";
import { thoughtRoutes } from "./routers/thoughtRoutes.js";
import { likeRoutes } from "./routers/likeRoutes.js";

export const app = express();

// Add middlewares to enable cors and json body parsing
app.use(cors());
app.use(bodyParser.json());

// Routes
app.use("/thoughts", thoughtRoutes);
app.use("/thoughts", likeRoutes);

// API documentation
app.get("/", (req, res) => {
const endpoints = expressListEndpoints(app);
res.send(endpoints);
});

// Error handling middleware
app.use(errorHandler);
14 changes: 14 additions & 0 deletions config/mongoDB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import mongoose from "mongoose";

export const connectToMongoDB = async() => {
const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/mongoAPI";

try {
await mongoose.connect(mongoUrl);
mongoose.Promise = Promise;
console.log("Connected to MongoDB");
} catch (error) {
console.error("Connection error", error.message);
process.exit(1);
}
}
22 changes: 22 additions & 0 deletions controllers/likeController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Thought } from "../models/thoughModel.js";

// post a like
export const postLike = async (req, res) => {
try {
const { id } = req.params;
const updatedThought = await Thought.findByIdAndUpdate(
id,
{ $inc: { hearts: 1 } },
{ new: true }
);

if (!updatedThought) {
return res.status(404).json({ error: "Thought not found" });
}

// Return the updated thought directly
res.status(200).json(updatedThought);
} catch (error) {
res.status(500).json({ error: error.message });
}
};
27 changes: 27 additions & 0 deletions controllers/thoughtController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Thought } from "../models/thoughModel.js";


// Get all thoughts
export const getThoughts = async (req, res) => {
try {
const thoughts = await Thought.find().sort({ createdAt: "desc" }).limit(20).exec();
// Just return the array of thoughts directly
res.status(200).json(thoughts);
} catch (error) {
res.status(500).json({ error: error.message });
}
};

// Post a new thought
export const postThought = async (req, res) => {
try {
const { message } = req.body;
const newThought = await new Thought({ message }).save();

// Return the newly created thought object directly
res.status(201).json(newThought);
} catch (error) {
res.status(400).json({ error: error.message });
}
};

8 changes: 8 additions & 0 deletions middleware/errorHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const errorHandler = (err, req, res, next) => {
console.error(err.stack);
res.status(500 || err.status).json({
success: false,
error: "Internal server error",
details: err.message
});
}
20 changes: 20 additions & 0 deletions middleware/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { body, validationResult } from "express-validator";

// Validation rules for creating a thought
export const validateThought = [
body("message")
.trim()
.notEmpty()
.withMessage("Message is required.")
.isLength({ min: 5, max: 140 })
.withMessage("Message must be between 5 and 140 characters."),
]

// General validation handler
export const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
};
20 changes: 20 additions & 0 deletions models/thoughModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import mongoose from "mongoose";

const thoughtSchema = new mongoose.Schema({
message: {
type: String,
required: true,
minlength: [5, "The message must be at least 5 characters long."],
maxlength: [140, "The message can't exceed 140 characters."]
},
hearts: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: () => new Date(),
}
});

export const Thought = mongoose.model("Thought", thoughtSchema);
32 changes: 23 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,29 @@
"start": "babel-node server.js",
"dev": "nodemon server.js --exec babel-node"
},
"author": "",
"author": "xing yin",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"@babel/core": "^7.26.0",
"@babel/node": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"cors": "^2.8.5",
"express": "^4.17.3",
"mongoose": "^8.0.0",
"nodemon": "^3.0.1"
}
}
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-list-endpoints": "^7.1.1",
"express-validator": "^7.2.0",
"mongodb": "^6.12.0",
"mongoose": "^8.9.1",
"nodemon": "^3.1.9"
},
"main": "server.js",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/xingyin2024/project-happy-thoughts-api.git"
},
"bugs": {
"url": "https://github.com/xingyin2024/project-happy-thoughts-api/issues"
},
"homepage": "https://github.com/xingyin2024/project-happy-thoughts-api#readme"
}
9 changes: 9 additions & 0 deletions routers/likeRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from "express";
import { postLike } from "../controllers/likeController.js";

const router = express.Router();

// Like a thought
router.post("/:id/like", postLike);

export { router as likeRoutes };
13 changes: 13 additions & 0 deletions routers/thoughtRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import express from "express";
import { getThoughts, postThought } from "../controllers/thoughtController.js";
import { validateThought, validate } from "../middleware/validation.js";

const router = express.Router();

// Get all thoughts
router.get("/", getThoughts);

// Post a new thought
router.post("/", validateThought, validate, postThought);

export { router as thoughtRoutes };
40 changes: 19 additions & 21 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import cors from "cors";
import express from "express";
import mongoose from "mongoose";
import { connectToMongoDB } from "./config/mongoDB.js";
import { app } from "./app.js"
import dotenv from "dotenv";

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
dotenv.config();

const port = process.env.PORT || 8080;
const app = express();

// Add middlewares to enable cors and json body parsing
app.use(cors());
app.use(express.json());
(async () => {
try {
// Connet to mongoDB
await connectToMongoDB();

// Start defining your routes here
app.get("/", (req, res) => {
res.send("Hello Technigo!");
});
// Start the server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
} catch (error) {
console.log("Failed to start server:", error.message);
process.exit(1); // Exit the process on failure
}
})();

// Start the server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
// Simplify server.js to handle only the server initialization and database connection