Skip to content

Node.JS, Express, Mongoose, MongoDb, JWT Objectives and Key Results API

Notifications You must be signed in to change notification settings

udemonia/okr-api

Repository files navigation

Objectives and Key Results

OKRs are comprised of Objectives (some desireable outcome you'd like to produce) and Key Results (measurable indicators to track success). OKRs are used to define how to track goals with measurable actions that gage success. A 70% success rate is typically desirable

Good OKRs

Typically, you want 3-5 Objectives to strive for over a fiscal quarter

Each Objective should have 3-5 Key Results that measure success in achieving the Objective

Why this app?

OKR tracking software is cumbersome, overly complicated and requires a high LOE to set and track OKRs. It's also difficult to access browser software via a mobile device and vice versa

Our goal in creating the app

Define an Objective and the time frame - typically a fiscal quarter. e.g. Q3 2021 (2021-07-01 - 2021-09-30)

Define some Key Results you can measure that indicate the success in achieving the Objective

Once the time frame begins, keep your Key Results up to date by simply updating the 'current value'

Objective and key result % complete tracking

Current value / target value = key result % completed

The avg. of all key results % completed becomes the objective % completed

Tech Stack

Backend

  • Node.JS
  • MongoDb
  • Mongoose

Frontend

  • React Native (mobile)
  • React (browser)

Objective Data Structure

  "atRisk": false,
  "percentComplete": 0,
  "_id": "6117e320fd7d720fa058935f",
  "name": "Take A Family Vacation",
  "description": "We need a Family Vacation, Preferably somewhere in Europe",
  "objectiveStartDate": "2021-07-01T00:00:00.000Z",
  "objectiveEndDate": "2021-09-30T00:00:00.000Z",
  "user": "60e2f4d5a85e1c5ba5fc999e",
  "slug": "take-a-family-vacation",
  "__v": 0

Key Result Structure

Each Key Result belongs to an Objective

        "atRisk": true,
        "progress": 50,
        "currentValue": 4000,
        "targetValue": 8000,
        "_id": "6117b8a43dafef88ed45c367",
        "name": "Save 8 Grand within the next 8 months",
        "description": "8 grand should allow us the funds needed for a all inclusive vacation",
        "objective": {
            "atRisk": false,
            "percentComplete": 50,
            "_id": "6117a57dcfe348f9d7865846",
            "name": "Take A Family Vacation",
            "description": "We need a Family Vacation, Preferably somewhere in Europe",
            "user": "60e2f4d5a85e1c5ba5fc995e",
            "slug": "brandon-go-on-a-vacation",
            "__v": 0,
            "id": "6117a57dcfe348f9d7865846"
        },
        "user": "60e2f4d5a85e1c5ba5fc995e",
        "tasks": [],
        "slug": "save-8-grand-within-the-next-8-months",
        "__v": 0,
        "id": "6117b8a43dafef88ed45c367"

How to Install

Clone/Fork the repro

npm install

create a .env file in config folder - see exampleEnv for environment variables

How to run local dev

Once you've updated the .evn with your mongo db connection url, and ran 'npm install', run the below

npm run dev

API Documentation

In the examples below, we're on localhost listening on port 2002 -> spinning up the server with npm run dev will output the port number or you can find it in the app.js file

User Creation, Login and Authorization

curl --location -g --request POST '{{url}}{{port}}/api/v1/auth/registration' \
--data-raw '{
    "name": "what ever you want here!",
    "email": "yourEmail@YourEmail.com",
    "password": "superSecretPasswordOfYourChoosing!"
}'

All routes are password protected with JWT Tokening, in order to create, read, update, or delete resources, you need an Auth Token

curl --location -g --request POST '{{url}}{{port}}/api/v1/auth/login' \
--data-raw '{
    "email": "yourEmail@yourEmail.com",
    "password": "yourPassWordHere"
}'

Example Response

{
    "success": true,
    "token": "JWT Token Here"
}

If you're using a http client like Postman, you can store the token response as a collection variable. In the example below, we're storing the JWT response value as 'token' and passing Authorization: Bearer {{token}} on all subsequent calls

Postman Collection Variables

Objective Routes

Post - Create an Objective

api/v1/objectives

curl --location --request POST 'http://localhost:2002/api/v1/objectives' \
--header 'Authorization: Bearer {{token}}' \
--data-raw '{
    "name": "Brandon - Take a Family Vacation",
    "description": "Lambert Family Vacation to Europe",
    "atRisk": false,
    "objectiveStartDate": "2021-07-01",
    "objectiveEndDate": "2021-09-30"
}'

Get Objectives

api/v1/objectives

curl --location --request GET 'http://localhost:2002/api/v1/objectives' \
--header 'Authorization: Bearer {{token}}'

Get Single Objective

api/v1/objectives/{{objective_id}}

curl --location --request GET 'http://localhost:2002/api/v1/objectives/{{resourceId}}' \
--header 'Authorization: Bearer {{token}}'

Update Objective

api/v1/objectives/{{objective_id}}

curl --location --request PUT 'http://localhost:2002/api/v1/objectives/60ddd3bd80473e478168f479' \
--header 'Authorization: Bearer {{token}}' \
--data-raw '{
    "name": "Updated Objective Name Here!"
}'

Delete Single Objective

api/v1/objectives/{{objective_id}}

curl --location --request DELETE 'http://localhost:2002/api/v1/objectives/{{resourceId}}' \
--header 'Authorization: Bearer {{token}}'

Key Results

Create a Single Key Result

Key Results belong to an objective, this is handled with nested routing

/api/v1/objectives/{{objective_id}}/keyresults

append '/keyresults' to the end of the single objective call and update the method to POST to create a key result for the objective

curl --location -g --request POST '{{url}}{{port}}/api/v1/objectives/60ddd4d0599562570c8896e5/keyresults' \
--header 'Authorization: Bearer {{token}}' \
--data-raw '{
    "name": "Save Enough Money for Food, Air Travel, AirBNB and Attractions!",
    "description": "Save Enough Money for a Family Trip to a European Capital!",
    "currentValue": 4000,
    "targetValue": 8000,
    "atRisk": true
}'

Example Response

in the response, the 'progress' field is dynamically created

{
    "success": true,
    "data": {
        "atRisk": true,
        "progress": 50,
        "currentValue": 4000,
        "targetValue": 8000,
        "_id": "6117c12d7fe0d3fd8c7c6307",
        "name": "Save Enough Money for Food, Air Travel, AirBNB and Attractions!",
        "description": "Save Enough Money for a Family Trip to a European Capital!",
        "objective": "6117c0d27fe0d3fd8c7c62eb",
        "user": "60e2f4d5a85e1c8ba5hx456b",
        "tasks": [],
        "slug": "save-enough-money-to-go-to-europe!-3",
        "__v": 0
    }
}

Get Key Results for an Objective

/api/v1/objectives/{{objective_id}}/keyresults

curl --location -g --request GET '{{url}}{{port}}/api/v1/objectives/60ddd3bd80473e478168f479/keyresults' \
--header 'Authorization: Bearer {{token}}'

Key Result Example Response

Objective data is virtualized and included in the JSON response - see Mongoose Virtuals for more info

{
    "success": true,
    "count": 2,
    "pagination": {},
    "data": [
        {
            "atRisk": true,
            "progress": 25,
            "currentValue": 2000,
            "targetValue": 8000,
            "_id": "6117c0e07fe0d3fd8c7c62ef",
            "name": "Save Enough Money for Food, Air Travel, AirBNB and Attractions!",
            "description": "Save Enough Money for a Family Trip to a European Capital!",
            "objective": {
                "_id": "6117c0d27fe0d3fd8c7c62eb",
                "name": "Brandon - Go On a Vacation",
                "description": "We need a Family Vacation:",
                "id": "6117c0d27fe0d3fd8c7c62eb"
            },
            "user": "60e2f4d5a85e1c5ba5fc995e",
            "tasks": [],
            "slug": "save-enough-money-to-go-to-europe!",
            "__v": 0
        }
    ]
}

Get All Key Results

api/v1/keyresults

curl --location -g --request GET '{{url}}{{port}}/api/v1/keyresults' \
--header 'Authorization: Bearer {{token}}'

Get a Single Key Result

api/v1/keyresults/{{keyResults_id}}

curl --location -g --request GET '{{url}}{{port}}/api/v1/keyresults/{{keyResult_id}}' \
--header 'Authorization: Bearer {{token}}'

Update Key Result

api/v1/keyresults/{{keyResult_Id}}

curl --location -g --request PUT '{{url}}{{port}}/api/v1/keyresults/60e2fb661395cec29980f3f5' \
--header 'Authorization: Bearer {{token}}' \
--data-raw '{
    "name": "Updated Key Result Name"
}'

Delete Key Result

api/v1/keyresults/{{keyResult_Id}}

curl --location -g --request Delete '{{url}}{{port}}/api/v1/keyresults/{{KeyResult_id}}' \
--header 'Authorization: Bearer {{token}}' \
--data-raw '{
    "name": "Updated Key Result Name"
}'

Mongo Query Options

On the 'select all' calls, you can pass logical operators for filtering down the results

Working off of API Master Class course (udemy.com - Brad Traversy), we're using the same type of Advanced Filtering options.

api/v1/objectives?select=description,name,atRisk

curl --location --request GET 'http://localhost:2002/api/v1/objectives?select=description,name,atRisk' \
--header 'Authorization: Bearer {{token}}'
  • select

  • sort

  • page

  • limit

Mongo operators

[lt] - less than [lte] - less than or equal to [gt] - greater than [gte] - greater than or equal to [in] - in

Releases

No releases published

Packages

No packages published