Notepads App built with NodeJS, ExpressJS, MongoDB, AngularJS and Bootstrap.
JavaScript HTML CSS
Fetching latest commit…
Cannot retrieve the latest commit at this time.

README.md

Notepads App built with NodeJS, ExpressJS, MongoDB, AngularJS and Bootstrap. Multiuser application that works with multiple Notepads in different Categories.

Build Status Coverage Status

npm: Dependency Status Bower: Dependency Status

See it in action here. This is the official application domain. It is used also by the mobile Android app (built with Ionic/AngularJS) too.

Tech

First I must mention that (most of)the (back-end)code is written in ES6: let/Promise/generators/fat arrow functions/modules/default function parameters/template literals. I use dynamic transpiling with Babel in the app and tests. Later I may refactor the generators+promises code to async functions. Also now with the latest NodeJS I can try dropping Babel and use the ES6 code as it is running natively in Node.

And now what the app uses:

Node.js + Express.js = RESTful API that uses Facebook auth and a custom accessToken after that. The API uses pure JSON communication.

The index page is loaded through Express.js and is an ejs template. After that Angular.js is loaded and handles everything else converting the app to a SPA.

Passport is used for Facebook auth through the site and fbgraph is used to handle the same for the API.

The API is used fully from the Notepads Ionic application on Google Play.

MongoDB is used as a database. Node.js + Mongoose.js: to create the models and connect to it.

Mocha, proxyquire and supertest are used for unit and integration testing. Protractor is used for end-to-end testing. Istanbul is used for coverage. I count on its future source maps parsing for ES6 coverage.

Install

Install Java for the Selenium server.

Install the global tools:

npm install -g grunt-cli bower

Go to the project's directory and run (also installs the bower's and Selenium server's packages):

npm install

Copy config/app.conf.json.dist to config/app.conf.json and edit it for your environment (set session SECRET, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, MONGODB_URI).

After that run the app with:

npm start

Or for developing:

nodemon src/app.js

The application runs in debug mode by default. That's why you need to set the NODE_ENV environment variable while starting it for example in production:

export NODE_ENV=production && forever /myapp/src/app.js

Testing

Back-end unit and integration tests (you may provide FB app id and secret in app.conf.json):

npm test

Front-end unit tests:

npm run karma

End-to-end tests (must have FB email and pass in app.conf.json):

npm run e2e

What can you do with this app

First I must say that this is one of my first apps after I've read the great book Your first app: node.js by Jim Schubert and so it is influenced by it and by the code in the book's repo.

Now about the application. You sign up with Facebook. No extra information is needed for this app and only your photo and name will be used and stored on the server as well as the Facebook Id to associate the user created in the DB with the one logging in.

After you log in you will load the Dashboard where you will be provided with one Sample category and one example Notepad called "Read me". You can delete them now or later.

Every Notepad shows its date of creation on the bottom as well as 2 buttons for edit and delete.

On the Dashboard the Notepads are "compressed" in small rectangles. This is usually enough for the most cases. But if you have a Notepad with a lot of text, you can press Edit and will see it in the way you saved it. In the mobile Android version of the application(see the link on the top) you see the Notepad without having to click edit.

Also the names Notepads and Notepad are used intentionally to differentiate this app from many others with the name Notepad/Notes etc. Think of the Notepad as a note and the Category as a notepad.

You can add a couple of Categories and after that start adding Notepads in them. The easiest way to add a new Notepad is through the Dashboard (the home page after logged in). See how next to the Categories' names on the Dashboard there are small blue buttons "Add Notepad Here". You will use them most of the time. There is also a link on the top "Add Notepad" that does almost the same but without preselected category.

Clicking on the top link "Categories" you will go to a page where you can see all Categories created and will have the possibility to Add New, Edit or Delete a Category. Deleting a Category will also delete all Notepads in it. Be careful with that. There is always confirmation when you click on Delete Category or Delete Notepad.

How it works

The application is a SAP(Single Page Application). It is made in 2 parts: back-end and front-end. The back-end provides an API which is used by the front-end.

The back-end is created with NodeJS modules/JavaScript and ExpressJS as well as some helper libraries. The Passport NPM module is used to log in/log out the user and to make the connection with Facebook. At the same time the API uses the fbgraph NPM module to verify the facebook data sent to it while the user logges in from the mobile Android app.

ExpressJS Router makes my life easier when I needed to have separate configurations for the different API routes.

The front-end is made with AngularJS and Bootstrap. Angular's $http is used to call the API using GET/POST/PUT/DELETE methods.

When the API is used it needs a logged in user with Passport through the site or an accessToken generated when the user was created for the mobile Android application(or any other code that may use the API exclusively).

Thanks to MongoDB/Mongoose the DB structure will be automatically created on first use.

The API

Currently the API is situated at https://notepads.iliyan-trifonov.com/api/v1

Here's a table showing what requests can be made and their possible results:

    <tr>
        <td colspan="6">/categories</td>
    </tr>
    <tr>
        <td><sub><pre lang="javascript">GET /categories</pre></sub></td>
        <td><sub><pre lang="javascript">?token=xxx</pre></sub></td>
        <td><sub><pre lang="javascript">none</pre></sub></td>
        <td><sub>OK = success, INTERNAL_SERVER_ERROR</sub></td>
        <td><sub><pre lang="javascript">[{category objects}..], []</pre></sub></td>
        <td><sub><pre lang="javascript">

[ { "_id": "5599057e4f1b9b8826711e99", "name": "Sample category", "notepadsCount": 3 }, ... ]

    <tr>
        <td colspan="6">/users</td>
    </tr>
    <tr>
        <td><sub><pre lang="javascript">POST /users/auth</pre></sub></td>
        <td><sub><pre lang="javascript">none</pre></sub></td>
        <td><sub><pre lang="javascript">

{ "accessToken": "xxx" }

or

{ "fbId": "xxx", "fbAccessToken": "xxx" }


Endpoint Params(GET) Body(POST, PUT) Status returned Data returned Example result
/notepads
GET /notepads
?token=xxx
none
OK = success, INTERNAL_SERVER_ERROR
[{notepad objects}..], []
[
    {
        "_id": "5599057e4f1b9b8826711e9a",
        "title": "notepad titlte",
        "text": "notepad text",
        "category": "5599057e4f1b9b8826711e99"
    },
    {
        "_id": "5599057e4f1b9b8826711e9c",
        "title": "notepad titlte 2",
        "text": "notepad text 2",
        "category": "5599057e4f1b9b8826711e99"
    },
    ...
]
            
GET /notepads
?insidecategories=1&token=xxx
none
OK = success, NOT_FOUND, INTERNAL_SERVER_ERROR
[
    {category objects-->
        notepads:[
            {notepad objects...}
        ]
    }
    ..
],
[]
[
    {
        "_id":"5599057e4f1b9b8826711e99",
        "name":"category name",
        "notepadsCount": 1,
        "notepads": [
            {
                "_id": "5599057e4f1b9b8826711e9a",
                "title": "notepad title",
                "text": "notepad text",
                "created": "2015/07/05 13:22:54"
            },
            {
                "_id": "5599057e4f1b9b8826711e9c",
                "title": "notepad title 2",
                "text": "notepad text 2",
                "created": "2015/07/05 13:23:28"
            },
            ...
        ]
    },
    ...
]
        
POST /notepads
?token=xxx
{
    title: "new title",
    text: "new text",
    category: "5599057e4f1b9b8826711e99",
}
            
CREATED = success, BAD_REQUEST, INTERNAL_SERVER_ERROR
{notepad object}, {}
{
    "__v": 0,
    "category": "5599057e4f1b9b8826711e99",
    "title": "new title",
    "text": "new text",
    "user": "5599057e4f1b9b8826711e98",
    "_id": "559968509ae0090c22e76e92"
}            
            
GET /notepads/:id
?token=xxx
none
OK = success, NOT_FOUND, INTERNAL_SERVER_ERROR
{notepad object}, {}
{
    "_id": "5599057e4f1b9b8826711e9a",
    "title": "new title",
    "text": "new text",
    "category": "5599057e4f1b9b8826711e99"
}
            
PUT /notepads/:id
:id is ObjectId(), ?token=xxx
{
    title: "updated title",
    text: "updated text",
    category: "5599057e4f1b9b8826711e99",
}
            
CREATED = success, BAD_REQUEST, NOT_FOUND, INTERNAL_SERVER_ERROR
{notepad object}, {}
{
    "_id": "5599057e4f1b9b8826711e9a",
    "title": "new title",
    "text": "new text",
    "category": "5599057e4f1b9b8826711e99",
    "user": "5599057e4f1b9b8826711e98",
    "__v": 0
}
            
DELETE /notepads/:id
:id is ObjectId(), ?token=xxx
none
NO_CONTENT = success, NOT_FOUND, INTERNAL_SERVER_ERROR
none, {}
none on success or {}
POST /categories
?token=xxx
{
"name": "new name"
}
CREATED = success, BAD_REQUEST, INTERNAL_SERVER_ERROR
{category object}, {}
{
"__v": 0,
"name": "new name",
"user": "5599057e4f1b9b8826711e98",
"_id": "559a4fa0c29f8ea009271b83",
"notepadsCount": 0
}
GET /categories/:id
:id is ObjectId(), ?token=xxx
none
OK = success, BAD_REQUEST, NOT_FOUND, INTERNAL_SERVER_ERROR
{category object}, {}
{
"_id": "559a4fa0c29f8ea009271b83",
"name": "new name"
}
PUT /categories/:id
:id is ObjectId(), ?token=xxx
{
"name": "updated name"
}
CREATED = success, BAD_REQUEST, NOT_FOUND, INTERNAL_SERVER_ERROR
{category object}, {}
{
"_id": "559a4fa0c29f8ea009271b83",
"name": "updated name",
"user": "5599057e4f1b9b8826711e98",
"__v": 0,
"notepadsCount": 0
}
DELETE /categories/:id
:id is ObjectId(), ?token=xxx
none
NO_CONTENT = success, NOT_FOUND, INTERNAL_SERVER_ERROR
none, {}
none on success or {}
OK/CREATED = success, BAD_REQUEST, UNAUTHORIZED, INTERNAL_SERVER_ERROR
{user object},
{accessToken: "xxx"}
{
"_id": "5599057e4f1b9b8826711e98",
"accessToken": "xxx",
"facebookId": "xxx",
"name": "FB user name",
"photo": "https://scontent.xx.fbcdn.net/xxxxx.jpg...",
"__v": 0,
"notepads": [
"559968509ae0090c22e76e92",
"559968ee9ae0090c22e76e93",
"55996ac39ae0090c22e76e94",
...
],
"categories": [
"5599057e4f1b9b8826711e99",
"559a4fa0c29f8ea009271b83",
...
]
}
or
{
"accessToken": "xxx"
}

TODO

More integration and e2e tests. 100% unit tests.

Convert all possible code to ES6 while keeping the compatibility with Node.js v10+. This one is already in progress and you can see in the code that ES5 and ES6 syntax work together.

Remove transpiling after Node.js and io.js merge and when support for the ES6 code here is full.

Covert the front-end code to ES6 (with Babel).

Use ES7 code where the transpilers allow it.

Analytics