Memenese
This is a Backend API of a social networking platform in Node.js
Radium uses a number of open source projects to work:
- Chai , Mocha & Supertest - Testing & Assertion
- Knex.js - Knex.js is a SQL query builder.
- Joi - Schema description language and data validator
- dotenv - Loads environment variables from a .env file into process.env
- Winston - Used for logging in both console and log file for easy debugging
- Multer - For handling multipart form-data
- Express Async Errors - Handles async errors.
- Express Rate Limit - Limit repeated requests to public APIs and/or endpoints such as password reset
- Jsonwebtoken - for Authentication/ Authorization
- Express - fast node.js network app framework
- MySQL - for database
- TypeScript - Better error detection and autocompletion
- CRUD posts, comments, users etc.
- Authentication, Authorization
- RateLimit to specific endpoints (or whole api)
- Validation of all input (Joi)
- Follow MC (of MVC)
Routes
router.get("/", UserController.all);
router.post("/signup", validate(schema.userRegister), UserController.signup);
router.post("/login", validate(schema.userLogin), UserController.login);
router.get("/:id", UserController.one);
router.put("/update", verifyAuth, validate(schema.userUpdate), UserController.update);
router.get("/:id/posts", UserController.posts);
export default router;
SignUp
export const signup = async (request: Request, response: Response, next: NextFunction) => {
const salt = await bcrypt.genSalt(10);
const hashed = await bcrypt.hash(request.body.password, salt);
request.body.password = hashed;
const user: User = await db("users").insert(request.body);
logger.info(`${request.body.email} REGISTERED`, user);
response.status(201).send(user);
};
Login
export const login = async (request: Request, response: Response, next: NextFunction) => {
const user: User = await db("users")
.where({ email: request.body.email })
.first();
const isValid = await bcrypt.compare(request.body.password, user.password);
if (isValid) {
var token = jwt.sign(
{ email: user.email, id: user.id, type: user.user_type, username: user.username },
process.env.JWT_KEY,
{
expiresIn: "7d"
}
);
} else {
logger.info(`AUTH FAILED: ${request.user.email}'s password does't match`);
response.status(HttpStatusCode.BAD_REQUEST).end();
}
logger.info(`${request.body.email}' LOGGED in`);
response.status(HttpStatusCode.OK).send(token);
};
Schema
CREATE TABLE `users` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(30) NOT NULL UNIQUE,
`email` VARCHAR(100) NOT NULL UNIQUE,
`password` VARCHAR(300) NOT NULL,
`img_url` VARCHAR(100),
`first_name` VARCHAR(100),
`last_name` VARCHAR(100),
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NULL,
`last_ip` VARCHAR(100),
`last_online` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
`dob` DATE,
`country` VARCHAR(30) NULL,
`user_type` varchar(10) NOT NULL DEFAULT 'normal',
PRIMARY KEY (`id`)
);
Test
describe("TEST /users", () => {
before("Create Post", done => {
supertest(app)
.post("/api/v1/posts/")
.set({
"Content-Type": "application/json",
Authorization: "Bearer " + process.env.JWT_TEST
})
.field("title", "TEST_POST")
.attach("file", "test/file/img.jpg")
.expect(201)
.then(res => {
postid = res.body[0];
assert.isNumber(postid);
done();
});
});
it("Fetch ONE post", () => {
return supertest(app)
.get("/api/v1/posts/1")
.expect(200)
.then(res => {
const body = res.body;
expect(body).to.have.property("id");
expect(body).to.have.property("title");
expect(body).to.have.property("file_path");
expect(body).to.have.property("user_id");
});
});
it("Generate feed", () => {
return supertest(app)
.get("/api/v1/posts/")
.expect(200)
.then(res => {
const body = res.body;
expect(body).to.be.an("array");
});
});
//...
after("Delete Post", done => {
supertest(app)
.delete("/api/v1/posts/" + postid)
.set({
"Content-Type": "application/json",
Authorization: "Bearer " + process.env.JWT_TEST
})
.expect(200, done);
});
});