DEV Community

Jay
Jay

Posted on

The web built by node and vue, imitate Twitter's UI and feature

Project Link
This project is built using Node and Vue.

The target is learning Vue framework and technique of backend to implement a SPA website.

All right of picture and sign is reserved for Twitter.

Used techniques, tools and packages by this project are not actually used by Twitter.

Welcome technical exchange, if this project has mistake of code or concept of programming, let me know, thanksπŸ‘

Demo

Main used package

Build Setup

Install all dependency packages: (assume has installed Node and NPM)

npm install
Enter fullscreen mode Exit fullscreen mode

Serve with hot reload at localhost:8080:

This step only can display frontend, backend server is not started yet.

npm run dev
Enter fullscreen mode Exit fullscreen mode

Run server of MongoDB: (assume has installed)

mongod --dbpath=YOUR_PATH_TO_STORE_DATA
Enter fullscreen mode Exit fullscreen mode

Run backend server listening 3001 port on development environment to handle API request:

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Uint test of server API:

npm run unit
Enter fullscreen mode Exit fullscreen mode

If all of unit test is pass, we can link in localhost:8080 and should work(sign up, login, etc...) successfully.

Above steps building application are enough for us to learn Vue and Node. However, if we want to deploy our project to cloud, below steps
are required.

Bundle to deploy

Bundle frontend(Vue) code:

npm run build
Enter fullscreen mode Exit fullscreen mode

Run server on development environment at localhost:3001:

The different with above step is now we can access our frontend page at localhost:3001 and no longer link to localhost:8080.
It means that the server will response bundled frontend page if the http request URL is not for API request.

npm run start:dev
Enter fullscreen mode Exit fullscreen mode

Run server on production environment:

When deploy to cloud, we take Heroku as a example, the Heroku will auto run npm start
by default. And we just auto take environment variables(Ex: PORT) of Heroku system to set up our server to work (Server of
MongoDB to connect should be configured by yourself)

npm start           #same as 'npm run start:prod'
Enter fullscreen mode Exit fullscreen mode

Custom configurations can be set at /server/config/config.json

Simple Introduction

Express

Official document

Use RESTful routes to handle http request.

const app = require('expess')

app.get('/', (req, res, next) => {
  res.json({
    res: 'This is GET method'
  })
})
app.post('/', (req, res, next) => {
    res.json({
      res: 'This is POST method'
    })
})
app.delete('/', (req, res, next) => {
  res.json({
    res: 'This is DELETE method'
  })
})
app.update('/', (req, res, next) => {
  res.json({
    res: 'This is UPDATE method'
  })
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

Official doeument

Use relational database.

This project has three models:

  • Users
  • Posts
  • Comments

Schema setting:

const userSchema = mongoose.Schema({
  posts: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Posts'
  }],
  //...
})
const postSchema = mongoose.Schema({
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Users'
  },
  comments: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Comments'
  }],
  //...
})
const commentSchema = mongoose.Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Users'
  },
  target: {
    model: String,
    id: mongoose.Schema.Types.ObjectId()
  },
  //...
})

const userModel = mongoose.Model('Users', userSchema)
const postModel = mongoose.Model('Posts', postSchema)
const commentModel = mongoose.Model('Comments', commentSchema)
Enter fullscreen mode Exit fullscreen mode

Get populated data:

userModel.findById(USER_ID)
  .then(user => {
    if (!user) {
      //...
    }

    let opt = {
      path: 'posts',
      populate: {
        path: 'comments'
      }
    }

    user.populate(opt).execPopulate()
      .then(populatedUser => {
        // Do what tou want to do
      }).
      catch(e => {
        //...
      })
  })
  .catch(e => {
    //...
  })
Enter fullscreen mode Exit fullscreen mode

Jsonwebtoken

Official document

Create an token and it will be invalid after 1 hour.

You can put some data into token to let server know this token's owner and information.

const jwt = require('jsonwebtoken')

const token = jwt.sign({
  id: USER_ID,
  access: 'auth',
  exp: Math.floor(Date.now() / 1000) + (60 * 60 * 1)
}, 'YOUR_SECRET_KEY')
Enter fullscreen mode Exit fullscreen mode

Token verification:

try {
  let data = jwt.verify(RECEIVED_TOKEN, 'YOUR_SECRET_KEY')
} catch (e) {
  // Verify fail
}
Enter fullscreen mode Exit fullscreen mode

Vue

Official document

The following picture shows the life cycle of a instance component.

I think it is the most important thing to understand each event when will be invoked.

Vue component's life cycle

If we have the component needs props of 'userID' to get user's info async.

When the components is instanced, function of created will be invoked and get user's information by current 'userID'.
But if the next route also has this component and has different props of 'userID', this component is reused rather than
instance a new component again. At this time the created function is not invoked, so the other method is using watch
property to monitor the 'userID' props change or not, if the indicated target change, the function you set will be invoked.

Vue.component('your-component', {
  props:['userID'],
  data: function () {
    return {
      user: null
    }
  },
  created() {
    this.getUserInfo()
  },
  watch: {
    // here is important
    'userID': 'getUserInfo'
  },
  method: {
    getUserInfo() {
      // Some http Request to get user information from server
    }
  },
  template: '<div v-if="user">{{ user.name }}</div>'
})
Enter fullscreen mode Exit fullscreen mode

Top comments (6)

Collapse
 
iamelme profile image
elme delos santos

newbie question!
why do you put the ref in an array object E.g

posts: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Posts'
  }],
Collapse
 
jayzang profile image
Jay • Edited

It lets you reference documents in other collections.
When you call 'populate' function, mongoose will automatically replacing the specified paths in the document with document(s) from other collections (Here is Posts collection).

Example:
If we have a user data:

{
    "_id": "123",
    "name": "Jay",
    "posts": ["456"]
}

And a post data:

{
    "_id": "456",
    "content": "Hello"
}

When you use below code, you can get the populated document

userModel.findOne({ _id: "123"})
    .then(userDoc => {
        userDoc.populate({path: "posts"}).execPopulate()
            .then(populatedDoc => {
                console.log(populatedDoc.toObject())
                // {
                //     "_id": "123",
                //     "name": "Jay",
                //     "posts": [{
                //         "_id": "456",
                //         "content": "Hello"
                //     }]
                // }
            })
    })

Detail usage can look office document:
mongoosejs.com/docs/populate.html

Collapse
 
iamelme profile image
elme delos santos

is it the same as without a bracket?

posts: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Posts'
  },
Thread Thread
 
jayzang profile image
Jay • Edited

if you do so, the posts only can reference one post.
Using bracket is to tell mongoose that posts is array type and data in the array is type of mongoose.Schema.Types.ObjectId which ref to Posts model.
So one user can have many posts.

Here more information

Thread Thread
 
iamelme profile image
elme delos santos

ahh okay thanks :D

Thread Thread
 
jayzang profile image
Jay

Hope I have help you~

By the way, I am very embarrassed about my poor english...