Skip to content

AndreiRadchenko/nodejs-rest-api

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GoIT Node.js Course Homework

Unit test for login controller

By using Jest library, I created controllers/users/login.test.js module for testing login rout. Run: npm test

Cloudinary

In the branch cloudinary saving avatar on the external cloud storage cloudinary has implemented. See controllers/users/updateAvatar for detail.

Jimp library for image transform

Upload image using multipart/form-data request and multer lib, resize image before saving using Jimp lib (helpers/resizeAndMoveAvatar.js)

REST API

The REST API to the example app is described below. Base url: https://rendercontacts.onrender.com

Authorization routes

Register: POST /api/auth/register

Request details
Headers:
Content-type: application-json

Body:
{
"name": "Andrii",
"email": "[email protected]",
"password": "123qweASD"
}

Response

Status: 201 Created

{
"message": "new user created",
"user": {
    "email": "[email protected]",
    "subscription": "starter"
    }
}

Email verification: GET /api/auth/verify/:verificationToken

Request details This request is sent by following link from the email user received after registration.
Request only need verificationToken in request string

Response

Status: 200 Ok

{
"message": "Email verification successful"
}

Resend verification email: POST /api/auth/verify

Request details In response to this request, the user will be sent an email with a verification link.
Headers:
Content-type: application-json

Body:
{
"email": "[email protected]"
}

Response

Status: 200 Ok

{
"message": "Email verification successful"
}

Login: POST /api/auth/login

Request details
Headers:
Content-type: application-json

Body:
{
"email": "[email protected]",
"password": "123qweASD"
}

Response

Status: 200 Ok

{
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDdhOGYzZjZmZTAwMDc3NTE0ZjI5MiIsImlhdCI6MTY3ODQ1MjkyMSwiZXhwIjoxNjc4NTM5MzIxfQ.Mcu_xtjfrqo6-IhXRPekd_YWBIzHhst2AbZZq7c7_eg",
"user": {
    "email": "[email protected]",
    "subscription": "pro",
    "avatarURL": "avatars/6407a8f3f6fe00077514f292.jpg"
    }
}

Current: GET /api/auth/current

Request details

You can use this route in your app to regain user connection in a case of the web page was closed, and valid token was saved in local Storage.

Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Response

Status: 200 Ok

{
"name": "Dima",
"email": "[email protected]",
"subscription": "pro",
"avatarURL": "avatars/6407a8f3f6fe00077514f292.jpg"
}

Update subscription: PATCH /api/auth

Request details
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Body:
{
"subscription": "pro"
}

Response

Status: 200 Ok

{
"message": "Subscription set to 'pro'",
"user": {
    "name": "Andrii",
    "email": "[email protected]",
    "subscription": "pro"
    }
}

Update avatar: PATCH /api/auth/avatars

Request details

Update user avatar using multer library

Headers:
Content-type: multipart/form-data
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

FormData:
{
"avatar": {imageFile}
}

Response

Status: 200 Ok

{
 "avatarURL": "http://res.cloudinary.com/dbm1pjejb/image/upload/v1678445440/rest-api-avatars/bx1yeff5ol1uas5frxyg.jpg"
}

Contacts collection routes

Add contact to user collection: POST /api/contacts

Request details
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Body:
{
"name": "Dima",
"email": "[email protected]",
"phone": "+380671234567"
}

Response

Status: 201 Created

{
"message": "new contact added",
"newContact": {
    "name": "Dima",
    "email": "[email protected]",
    "phone": "+380671234567",
    "favorite": false,
    "owner": "6402f61296dea959047b4037",
    "_id": "64031305e56210df7284fe18",
    "createdAt": "2023-03-04T09:44:37.108Z",
    "updatedAt": "2023-03-04T09:44:37.108Z"
    }
}

Get all user contacts: GET /api/contacts

Request details
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Response

Status: 200 Ok

{
"message": "Contacts list array in json format",
"contacts": [
    {
        "_id": "64031305e56210df7284fe18",
        "name": "Dima",
        "email": "[email protected]",
        "phone": "+380671234567",
        "favorite": false,
        "owner": {
            "_id": "6402f61296dea959047b4037",
            "name": "Andrii",
            "email": "[email protected]"
        }
    },
    {
        "_id": "64031513e56210df7284fe1b",
        "name": "Misha",
        "email": "[email protected]",
        "phone": "+380671234567",
        "favorite": false,
        "owner": {
            "_id": "6402f61296dea959047b4037",
            "name": "Andrii",
            "email": "[email protected]"
        }
    },
}

Get contact by id: GET /api/contacts/:id

Request details
    https://rendercontacts.onrender.com/api/contacts/64031305e56210df7284fe18
Headers: Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Response

Status: 200 Ok

{
"message": "Contact with Id: 64031305e56210df7284fe18",
"contact": {
    "_id": "64031305e56210df7284fe18",
    "name": "Dima",
    "email": "[email protected]",
    "phone": "+380671234567",
    "favorite": false,
    "owner": "6402f61296dea959047b4037",
    "createdAt": "2023-03-04T09:44:37.108Z",
    "updatedAt": "2023-03-04T09:44:37.108Z"
    }
}

Delete contact: DELETE /api/contacts/:id

Request details
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Response

Status: 200 Ok

{
"message": "contact deleted",
"result": {
    "_id": "64031305e56210df7284fe18",
    "name": "Dima",
    "email": "[email protected]",
    "phone": "+380671234567",
    "favorite": false,
    "owner": "6402f61296dea959047b4037",
    "createdAt": "2023-03-04T09:44:37.108Z",
    "updatedAt": "2023-03-04T09:44:37.108Z"
    }
}

Update contact's data: PUT /api/contacts/:id

Request details
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Body:
{
"name": "Anatolii2",
"email": "[email protected]",
"phone": "0673329751"
}

Response

Status: 200 Ok

{
"message": "contact with new fields",
"result": {
    "_id": "6401cc29ca13c7e6fdaf4ece",
    "name": "Anatolii2",
    "email": "[email protected]",
    "phone": "0673329751",
    "favorite": false,
    "owner": "6401a402df529b42f7f9223b",
    "createdAt": "2023-03-03T10:30:01.809Z",
    "updatedAt": "2023-03-04T12:52:23.629Z"
    }
}

Update contact's favorite flag: PATCH /api/contacts/:id/favorite

Request details
    https://rendercontacts.onrender.com/api/contacts/64031513e56210df7284fe1b/favorite
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Body:
{
"favorite": "true"
}

Response

Status: 200 Ok

{
"message": "contact with new fields",
"result": {
    "_id": "64031513e56210df7284fe1b",
    "name": "Misha",
    "email": "[email protected]",
    "phone": "+380671234567",
    "favorite": true,
    "owner": "6402f61296dea959047b4037",
    "createdAt": "2023-03-04T09:53:23.430Z",
    "updatedAt": "2023-03-04T13:21:49.759Z"
    }
}

Filter contacts by the 'favorite' flag: GET /api/contacts?favorite=true

Request details
https://rendercontacts.onrender.com/api/contacts?favorite=true
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Response

Status: 200 Ok

{
"message": "Contacts list array in json format",
"contacts": [
    {
        "_id": "64031513e56210df7284fe1b",
        "name": "Misha",
        "email": "[email protected]",
        "phone": "+380671234567",
        "favorite": true,
        "owner": {
            "_id": "6402f61296dea959047b4037",
            "name": "Andrii",
            "email": "[email protected]"
        }
    },
    {
        "_id": "6403152be56210df7284fe1e",
        "name": "Vika",
        "email": "[email protected]",
        "phone": "+380671234567",
        "favorite": true,
        "owner": {
            "_id": "6402f61296dea959047b4037",
            "name": "Andrii",
            "email": "[email protected]"
        }
    },
    {
        "_id": "64034ac4ed91c30518073bec",
        "name": "Dima",
        "email": "[email protected]",
        "phone": "+380671234567",
        "favorite": true,
        "owner": {
            "_id": "6402f61296dea959047b4037",
            "name": "Andrii",
            "email": "[email protected]"
        }
    }
]
}

Pagination and filtering: GET /api/contacts?page=1&limit=2&favorite=false

Request details
https://rendercontacts.onrender.com/api/contacts?page=1&limit=2&favorite=false
Headers:
Content-type: application-json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MDJmNjEyOTZkZWE5NTkwNDdiNDAzNyIsImlhdCI6MTY3NzkxNjk3NCwiZXhwIjoxNjc4MDAzMzc0fQ.seXRPf2_C11GkhcylP63rhgdTWJIozYrE8-K66u-beU

Response

Status: 200 Ok

{
"message": "Contacts list array in json format",
"contacts": [
    {
        "_id": "6403153be56210df7284fe21",
        "name": "Ira",
        "email": "[email protected]",
        "phone": "+380671234567",
        "favorite": false,
        "owner": {
            "_id": "6402f61296dea959047b4037",
            "name": "Andrii",
            "email": "[email protected]"
        }
    },
    {
        "_id": "64034af2ed91c30518073bef",
        "name": "Katya",
        "email": "[email protected]",
        "phone": "+380671234567",
        "favorite": false,
        "owner": {
            "_id": "6402f61296dea959047b4037",
            "name": "Andrii",
            "email": "[email protected]"
        }
    }
]
}

Unique fields in MongoDB collection, and handling conflicts error

  1. In Compass in collection tab navigate to Index -> create new index -> choose field that should bee unique -> check Create unique index

  2. In mongoose schema add prop unique: true to the field definition and add error handler to the schema: contactSchema.post('save', handleSchemaError);

contactSchema
const contactSchema = new Schema(
  {
    name: {
      type: String,
      required: [true, 'Set name for contact'],
      unique: true,
    },
    email: {
      type: String,
      unique: true,
    },
    phone: {
      type: String,
    },
    favorite: {
      type: Boolean,
      default: false,
    },
  },
  { versionKey: false, timestamps: true }
);

contactSchema.post('save', handleSchemaError);

  1. Create helper handleSchemaError which will be called while saving model with our schema. This helper callback is responsible for correct status code sending to frontend. (409 - conflict)
handleSchemaError
const isConflict = ({ name, code }) => {
  return name === 'MongoServerError' && code === 11000;
};

const handleSchemaError = (error, data, next) => {
  error.status = isConflict(error) ? 409 : 400;
  next();
};

module.exports = handleSchemaError;

About

Node.js backend application that uses express, mongoose, multer library

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 71.1%
  • Handlebars 27.9%
  • Shell 1.0%