Skip to content
Snippets Groups Projects
Commit cc234ed0 authored by Andrej Rasevic's avatar Andrej Rasevic
Browse files

adding exercise5

parent d25de1be
No related branches found
No related tags found
No related merge requests found
Showing
with 3526 additions and 0 deletions
# Exercise 3: Building our own Twitter API
## Due Date: Friday, January 22, 2021 11:59 PM
## Objectives: To build a complete API, including validations, schema, routing and controllers. Additionally learn about how to handle relational data in mongodb as well as user authentication.
## Overview
We will be building out a bare bones version of the twitter api. In this API you will be defining endpoints that allow you to create a user as well as create the tweets associated with that user. You will additionally be creating endpoints that will retrieve all the users stored in the database, all the tweets stored in the database and all the information for an individual user and their associated tweets.
## Project Implementation/Specifications
The files that comprise the API are `app.js`, the router files, the model schema and the controller files. The controller methods you will call in your router module have already been created for you. You will just need to invoke them along with the correct REST action at the specified endpoint. The files you will need to edit are the following:
1. `App.js`
At the bottom of `App.js` paste in the teaching staff login name and password as well as the mongodb connection string you created in `Exercise 2`. The section of the url string that you need to update has been referenced directly where it goes.
2. `User Schema`
You need to define the User Schema inside of `models/User.js`. A User has the following 5 fields:
* name - it should be a String and a required field.
* login - it should be a String and a required field. It represents the login a user will use to authenticate into the application system.
* password - it should be a String and a required field. When you add user authentication to the application stack you will use the login and password combination to authenticate a user.
* email - it should be a String and required.
* tweets - it should be defined like this:
```javascript
tweets: [{
type: Schema.Types.ObjectId,
ref: 'Tweet'
}]
```
3. `Tweet Schema`
You need to define the Tweet Schema inside of `models/Tweet.js`. A Tweet has the following 3 fields:
* content - it should be defined as a String and required.
* likes - it should be defined as a Number and required.
* dislikes - it should be defined as a Number and required.
4. `users router`
You need to define all of your routes(endpoints) inside of `routes/api/v1/Users.js`. The callback you invoke in your functions that define the routes should be one of the functions exported in the `UserController` module. Additionally, any endpoint that creates a new resource should utilize the validator also defined inside of the `UserController` module.
You will need to define the following 5 endpoints:
* `/api/v1/users` - this will return all the documents inside the `users` collection.
* `/api/v1/user` - this will generate a new user document inside of the `users` collection.
* `/api/v1/tweets` - this will return all the documents inside the `tweets` collection.
* `/api/v1/user/:id` - this will generate a new document inside the `tweets` collection as well as generate a reference to the document inside the `users` collection that has a value of `:id` for its `_id` field.
* `/api/v1/users/:id` - this will return the document that matches the `:id` request param as well as all the tweets that it references. __Note__: the operation that you will be performing in this endpoint inside of mongo is identical to a join method in a tradition SQL database.
## Sample result outputs from postman
successfully creating a user
![Create User reponse](postmanSampleResponses/create-user-response.png)
successfully creating a second user
![Create second user response](postmanSampleResponses/create-another-user-response.png)
returning all the users
![retrieve all the users](postmanSampleResponses/retrieve-all-users.png)
create a tweet for a user
![create tweet](postmanSampleResponses/create-tweet.png)
retrieve all tweets
![retrieve all tweets](postmanSampleResponses/retrieve-all-tweets.png)
retrieve a single user
![retrieve single user](postmanSampleResponses/retrieve-single-user.png)
exercises/exercise5/postmanSampleResponses/create-another-user-response.png

83.4 KiB

exercises/exercise5/postmanSampleResponses/create-tweet.png

98.3 KiB

exercises/exercise5/postmanSampleResponses/create-user-response.png

80.1 KiB

exercises/exercise5/postmanSampleResponses/retrieve-all-tweets.png

85.9 KiB

exercises/exercise5/postmanSampleResponses/retrieve-all-users.png

145 KiB

exercises/exercise5/postmanSampleResponses/retrieve-single-user.png

149 KiB

.DS_Store
node_modules
\ No newline at end of file
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const mongoose = require('mongoose');
const cors = require('cors')
const bodyParser = require('body-parser');
const usersRouter = require('./routes/api/v1/Users');
const app = express();
app.use(express.json());
app.use(cors)
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use('/api/v1/', usersRouter);
const dbuser = 'dbadmin'
const dbpass = 'dbpassword'
const mongoDB = `mongodb+srv://${dbuser}:${dbpass}@teaching-adb1b.mongodb.net/lectureExamples?retryWrites=true`;
mongoose.connect(mongoDB, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false });
const dB = mongoose.connection;
dB.on('error', console.error.bind(console, 'MongoDB connection error:'));
app.get("/", function(req, res) {
res.json({test: "response"});
});
module.exports = app;
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('twitter-api:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
GET http://localhost:3000/api/v1/users/5e2397f3f71874736e0e0773 HTTP/1.1
const db = require('../index');
const { body } = require('express-validator/check');
const { validationResult } = require('express-validator/check');
// Retrieve list of all Users
exports.user_list = function(req, res) {
db.User.find({})
.then(function(dbUsers) {
res.json(dbUsers);
})
.catch(function(err) {
res.json(err);
})
};
// Retrieve list of all tweets
// Tweets do not store a reference to whom they belong
exports.tweet_list = function(req,res) {
db.Tweet.find({})
.then(function(dbTweets) {
res.json(dbTweets);
})
.catch(function(err) {
res.json(err);
})
}
// Create a user
exports.create_user = function(req, res, next) {
try {
const errors = validationResult(req); // Finds the validation errors in this request and wraps them in an object with handy functions
if (!errors.isEmpty()) {
console.log(errors)
res.status(422).json({ errors: errors.array() });
return;
}
db.User.create(req.body)
.then(function(dbUser) {
// If we were able to successfully create a Product, send it back to the client
res.json(dbUser);
})
.catch(function(err) {
// If an error occurred, send it to the client
res.json(err);
});
} catch(err) {
return next(err)
}
}
// Create a tweet and reference it in the appropriate user
exports.create_tweet = function(req, res, next) {
try {
const errors = validationResult(req); // Finds the validation errors in this request and wraps them in an object with handy functions
if (!errors.isEmpty()) {
console.log(errors)
res.status(422).json({ errors: errors.array() });
return;
}
// Create a new tweet and pass the req.body to the entry
db.Tweet.create(req.body)
.then(function(dbTweet) {
// If a Tweet was created successfully, find one user with an `_id` equal to `req.params.id`. Update the User to be associated with the new Tweet
// { new: true } tells the query that we want it to return the updated User -- it returns the original by default
// Since our mongoose query returns a promise, we can chain another `.then` which receives the result of the query
return db.User.findOneAndUpdate({ _id: req.params.id }, {$push: {tweets: dbTweet._id}}, { new: true });
})
.then(function(dbUser) {
// If we were able to successfully update a User, send it back to the client
res.json(dbUser);
})
.catch(function(err) {
// If an error occurred, send it to the client
res.json(err);
});
} catch(err) {
return next(err)
}
}
// Retrieve a single user and all of the associated tweets
exports.get_user = function(req, res) {
// Using the id passed in the id parameter, prepare a query that finds the matching one in our db...
db.User.findOne({ _id: req.params.id })
// ..and populate all of the tweets associated with it
.populate("tweets")
.then(function(dbUser) {
// If we were able to successfully find a User with the given id, send it back to the client
res.json(dbUser);
})
.catch(function(err) {
// If an error occurred, send it to the client
res.json(err);
});
}
//custom validations
// only done on create methods
exports.validate = (method) => {
switch (method) {
case 'create_user': {
console.log('validating new user')
return [
body('name', 'name doesn\'t exist').exists(),
body('login', 'login doesn\'t exist').exists(),
body('password', 'password isn\'t long enough').isLength({min: 6}),
body('email', 'email doesn\'t exist').exists(),
body('email', 'email is not a valid email').isEmail()
]
}
case 'create_tweet': {
console.log('validating new tweet')
return [
body('content', 'content of tweet is too long').isLength({max: 150}),
]
}
}
}
module.exports = {
User: require("./models/User"),
Tweet: require("./models/Tweet"),
};
\ No newline at end of file
const mongoose = require("mongoose");
\ No newline at end of file
const mongoose = require("mongoose");
\ No newline at end of file
This diff is collapsed.
{
"name": "twitter-api",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www",
"devstart": "nodemon ./bin/www"
},
"dependencies": {
"async": "^2.6.2",
"body-parser": "^1.19.0",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"express": "~4.16.1",
"express-validator": "^5.3.1",
"mongoose": "^5.8.9",
"morgan": "~1.9.1"
},
"devDependencies": {
"nodemon": "^1.18.10"
}
}
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
const express = require('express');
const usersRouter = express.Router();
module.exports = usersRouter;
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment