{"id":21739321,"url":"https://github.com/supawite-peter/simple-iot-backend-microservices","last_synced_at":"2026-04-05T23:02:45.859Z","repository":{"id":264297348,"uuid":"892945707","full_name":"Supawite-Peter/simple-iot-backend-microservices","owner":"Supawite-Peter","description":"A simple IoT backend microservices based on nest.js framework","archived":false,"fork":false,"pushed_at":"2024-12-18T14:09:44.000Z","size":117,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-21T00:44:21.884Z","etag":null,"topics":["backend","iot","nestjs","typescript"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Supawite-Peter.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-23T05:40:21.000Z","updated_at":"2024-12-18T14:09:18.000Z","dependencies_parsed_at":"2024-11-23T08:18:56.335Z","dependency_job_id":"0308ecbd-67e0-4017-8480-347c20c8a441","html_url":"https://github.com/Supawite-Peter/simple-iot-backend-microservices","commit_stats":null,"previous_names":["supawite-peter/simple-iot-backend-microservices"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Supawite-Peter%2Fsimple-iot-backend-microservices","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Supawite-Peter%2Fsimple-iot-backend-microservices/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Supawite-Peter%2Fsimple-iot-backend-microservices/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Supawite-Peter%2Fsimple-iot-backend-microservices/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Supawite-Peter","download_url":"https://codeload.github.com/Supawite-Peter/simple-iot-backend-microservices/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244717337,"owners_count":20498283,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","iot","nestjs","typescript"],"created_at":"2024-11-26T06:08:29.669Z","updated_at":"2025-09-21T17:59:28.182Z","avatar_url":"https://github.com/Supawite-Peter.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Simple IoT Backend\n\n## Description\n\nA simple IoT backend server application based on the Nest.js framework. Serving user/device registration and database services through REST API.\n\n## Concept\n\nUsers can register an IoT device with specific topics, each of which stores the sensor values transmitted from that device, as illustrated in this diagram. \n\n![alt text](/docs/images/image.png)\n\nAdditionally, users have the flexibility to freely add or delete new devices and topics as needed. \n\nFurthermore, users can query the latest data or data from a preferred time period.\n\n## Planning Features\n\n- :white_check_mark: Users Management Module\n- :white_check_mark: Users Authentication Module \n- :white_check_mark: Devices Management Module\n- :white_check_mark: Devices Data Module\n- :white_check_mark: MonogoDB implementation on Users/Devices Module\n- :white_check_mark: Support Timestamp Devices Data\n- :white_check_mark: API Docs\n- :white_check_mark: Migrate User/Device/Topic to SQL DB\n- :white_check_mark: Dockerize / Compose\n- :white_check_mark: Caching\n- :white_check_mark: JWT Refresh/Access Token\n- :white_check_mark: JWT Cookie\n- :white_check_mark: MQTT Support\n- :black_square_button: API Key for Devices Data\n- :black_square_button: Swagger UI Page\n- :black_square_button: e2e Test\n- :black_square_button: User Role\n- :black_square_button: Aggregate Sensor Data\n- :black_square_button: Simple Web Front-end\n- ...\n\n## Run the project\n\n1. Create and populate `.env` file. (example on `.env.example`)\n\n3. Open `emqx.users.csv` and edit mqtt username and password as same as config in `.env` file \n\n2. Run docker compose\n\n    ```\n    docker-compose up -d\n    ```\n\n## MQTT\n\nThis application allows sending sensor data to an MQTT broker using the topic format:   `data/{deviceId}/{topic}`  \n\n### Access/Login to MQTT Broker\nTo access or log in to the MQTT broker:  \n1. Use the same username and password as registered in the `[POST]/users` endpoint.  \n2. If you want to use a separate MQTT password, send a `PATCH` request to the endpoint `/users/mqtt/password` with the new password in the request body.  \n\n**Note**: Setting a separate MQTT password will disallow logging in to the MQTT broker with your base password.\n\n### Payload Format\nThe payload must be a JSON object containing the following:  \n- `username`: Your registered username (string).  \n- `value`: The data value you want to send (number).  \n\n### Example\nTo update the `temp` topic for a device with ID `4`, you can send:  \n```json\n{\n  \"username\": \"your_username\",\n  \"value\": 5\n}\n```\nUsing the topic: `data/4/temp`  \nThis will update the value `5` for the `temp` topic of device ID `4`.\n\n### Important Note\n\n1. You can only update the value of a device ID and topic that you own or have registered.\n2. The `username` in the payload is required to verify ownership of the device and topic.\n\n### Future Update\nThe requirement for including username in the payload will be removed in an upcoming update.\n\n\n## API\n\n### Users\n\n#### Add or Remove User Account\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003ePOST\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/users\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Register new user account)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e None\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | username | required | string | string of username  |\n\u003e | password | required | string | string of password  |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `201` | `application/json` | `{\"id\": 1 ,\"username\": hello}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `409` | `application/json` | `{\"message\": \"Username already exists\",\"error\": \"Conflict\",\"statusCode\": 409}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/users' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"username\": \"hello\",\n\u003e    \"password\": \"world\"\n\u003e }'\n\u003e ```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eDELETE\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/users\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Delete user account)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `password` | required | string | string of password |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `{\"id\": 1, \"username\": \"hello\"}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User does not exist\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location --request DELETE 'http://localhost:3000/users' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"password\": \"world\"\n\u003e }'\n\u003e ```\n\n\u003c/details\u003e\n\n#### Update User MQTT Password\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003ePATCH\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/users/mqtt/password\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Update user mqtt password)\u003c/code\u003e\u003c/summary\u003e\n\n##### Note\n\nBy default if mqtt password is not set, base password will be used for mqtt authentication instead.\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | password | required | string | string of mqtt password  |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `201` | `application/json` | `{\"id\": 1 ,\"username\": hello}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User is not exist\", \"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --request PATCH\n\u003e --location 'http://localhost:3000/users/mqtt/password' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"password\": \"world\"\n\u003e }'\n\u003e ```\n\n\u003c/details\u003e\n\n\n#### Get User Detail\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eGET\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/users/{userId}\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Get user account detail)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n\n##### Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `userId` | required | number | targeted user id for details |\n\n##### Body\n\n\u003e None\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `{\"id\": 1, \"username\": \"hello\"}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User does not exist\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location --request GET 'http://localhost:3000/users/1' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json'\n\u003e ```\n\n\u003c/details\u003e\n\n---------------------------------------------------------\n\n### Auth\n\n#### Login\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003ePOST\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/auth/login\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Login and set access token and refresh token cookies)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e None\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `username` | required | string | string of username |\n\u003e | `password` | required | string | string of password |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `None` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Incorrect password\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User doesn't exist\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/auth/login' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"username\": \"hello\",\n\u003e    \"password\": \"world\"\n\u003e }'\n\u003e ```\n\n\u003c/details\u003e\n\n#### Refresh new access token\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eGET\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/auth/refresh\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(set new access token cookie)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `refreshToken` | string | a Refresh JWT Token Set from `/auth/login` |\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e None\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `None` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\", \"statusCode\": 401}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/auth/login' \\\n\u003e --header 'Cookie: refreshToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json'\n\u003e ```\n\n\u003c/details\u003e\n\n------------------------------------------------------\n\n### Devices\n\n#### Add or Remove Device\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003ePOST\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Register new device to user)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `name` | required | string   | Name of the device |\n\u003e | `topics` | optional | string[] or string | Topics to be registered |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `201` | `application/json` | `{\"id\": 1, \"name\": \"device1\", \"userId\": 1, \"topics\": [\"temp\", \"rh\"]}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Device name is missing\",\"statusCode\": 400}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/devices' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"name\": \"device1\",\n\u003e    \"topics\": [\"temp\",\"rh\"]\n\u003e}'\n\u003e ```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eDELETE\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Delete registered device)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e | name  |  type | data type | description |\n\u003e |-------|-------|-----------|-------------|\n\u003e | `id` | required | string or number | device id to be delete |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `{\"id\": 1, \"name\": \"device2\", \"userId\": 1, \"topics\": [\"temp\", \"rh\"]}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"Device with id {{deviceId}} was not found for user with id {{userId}}\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location --request DELETE 'http://localhost:3000/devices' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"id\": \"1\"\n\u003e }'\n\u003e ```\n\n\u003c/details\u003e\n\n#### Get Device Detail\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eGET\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices/{deviceId}\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Get device details)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n\n##### Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `deviceId` | required | number | targeted device id for details |\n\n##### Body\n\n\u003e None\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `{\"id\": 1, \"name\": \"device2\", \"userId\": 1, \"topics\": [\"temp\", \"rh\"]}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"Device with id {{deviceId}} was not found for user with id {{userId}}\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location --request DELETE 'http://localhost:3000/devices/1' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json'\n\u003e ```\n\n\u003c/details\u003e\n\n#### List User Owned Devices\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eGET\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(List every devices registered by current user)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n\n##### Parameters\n\n\u003e None\n\n##### Body\n\n\u003e None\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `[{\"id\": 1, \"name\": \"device1\", \"userId\": 1,  \"topics\": [\"temp\", \"rh\"]}]` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"No devices found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/devices' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}'\n\u003e ```\n\n\u003c/details\u003e\n\n--------------------------------------------------------------------\n\n#### Add or Remove Topics\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003ePOST\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices/{deviceId}/topics\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Add new topics to a device)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n##### Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `deviceId` | required | number | target device id to add topics |\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `topics` | required | string[] or string | topics to be added |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `201` | `application/json` | `{\"topicsAdded\": 1, \"topics\": [\"air\"]}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Topics are already registered\",\"statusCode\": 400}`|\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"Device with id {{deviceId}} was not found for user with id {{userId}}\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/devices/1/topics' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"topics\": \"air\"\n\u003e}'\n\u003e ```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eDELETE\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices/{deviceId}/topics\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Remove registered topic from a device)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n\n##### Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `deviceId` | required | number | target device id to delete topics |\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `topics` | required | string or string[] | topics to be removed |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `201` | `application/json` | `{\"topicsRemoved\": 1, \"topics\": [\"air\"]}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `400` | `application/json` | `{\"message\": \"Topics are not registered\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"Device with id {{deviceId}} was not found for user with id {{userId}}\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location --request DELETE 'http://localhost:3000/devices/1/topics' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e    \"topics\": \"air\"\n\u003e }'\n\u003e ```\n\n\u003c/details\u003e\n\n----------------------------------------------\n\n#### Sending Sensor Data to a Topic\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003ePOST\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices/{deviceId}/{topic}\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Sending Sensor Data to a Topic)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n##### Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `deviceId` | required | number | target device id to storing data |\n\u003e | `topic` | required | string | target topic to storing data |\n\n##### Body\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `payload` | required | object or object[] | `{timestamp: {{iso_timestamp}}, value: {{number}}}` |\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `201` | `application/json` | `[{ \"timestamp\": {{iso_timestamp}},\"value\":{{number}} }, ...]` |\n\u003e | `400` | `application/json` | `{\"message\": \"Validation failed\",\"statusCode\": 400}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Requester is not the owner of the device\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"Device with id {{device_id}} was not found\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/devices/1/temp' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json' \\\n\u003e --data '{\n\u003e   \"payload\": [\n\u003e        {\n\u003e            \"value\": 0\n\u003e        },\n\u003e        {\n\u003e            \"timestamp\": \"2024-10-18T08:54:50.318Z\",\n\u003e            \"value\" : 3\n\u003e        }\n\u003e    ]\n\u003e}'\n\u003e ```\n\n#### Example Response\n\n\u003e```javascript\n\u003e[\n\u003e    {\n\u003e        \"timestamp\": \"2024-10-18T10:57:26.776Z\",\n\u003e        \"value\": 0\n\u003e    },\n\u003e    {\n\u003e        \"timestamp\": \"2024-10-18T10:54:05.904Z\",\n\u003e        \"value\": 3\n\u003e    }\n\u003e]\n\u003e```\n\n\u003c/details\u003e\n\n----------------------------------------------\n\n#### Query Sensor Data\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eGET\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices/{deviceId}/{topic}/latest\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Query latest data from a topic)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n##### Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `deviceId` | required | number | target device id to storing data |\n\u003e | `topic` | required | string | target topic to storing data |\n\n##### Query Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `unix` | optional | boolean | true if want timestamp (ms) body to be in unix timestamp otherwise in iso timestamp (default) |\n\n##### Body\n\n\u003e None\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `{ \"timestamp\": {{iso/unix_timestamp}},\"value\":{{number}} }` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Requester is not the owner of the device\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"Device with id {{device_id}} was not found\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"No Latest Data Found\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/devices/1/temp/latest' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}'\n\u003e ```\n\n#### Example Response\n\n\u003e```javascript\n\u003e{\n\u003e    \"timestamp\": \"2024-10-18T10:57:26.776Z\",\n\u003e    \"value\": 0\n\u003e}\n\u003e```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n \u003csummary\u003e\u003ccode\u003eGET\u003c/code\u003e \u003ccode\u003e\u003cb\u003e/devices/{deviceId}/{topic}/periodic\u003c/b\u003e\u003c/code\u003e \u003ccode\u003e(Query sensor data in between time)\u003c/code\u003e\u003c/summary\u003e\n\n##### Authentication\n\n\u003e | cookie key | type | description |      \n\u003e |--------|------|-------------|\n\u003e | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |\n\n##### Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `deviceId` | required | number | target device id to storing data |\n\u003e | `topic` | required | string | target topic to storing data |\n\n##### Query Parameters\n\n\u003e | name | type | data type | description |\n\u003e |------|------|-----------|-------------|\n\u003e | `unix` | optional | boolean | true if want timestamp body to be in unix timestamp (ms) otherwise in iso timestamp (default) |\n\u003e | `from` | required | ISO String Datetime or Unix Timestamp (ms) | datetime indicating the starting point of requested data |\n\u003e | `to`   | required | ISO String Datetime or Unix Timestamp (ms) |  datetime indicating the end of requested data |\n\n##### Body\n\n\u003e None\n\n\n\n##### Responses\n\n\u003e | http code | content-type | response |\n\u003e |-----------|--------------|----------|\n\u003e | `200` | `application/json` | `[{ \"timestamp\": {{iso/unix_timestamp}},\"value\":{{number}}, ...]}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Unauthorized\",\"statusCode\": 401}` |\n\u003e | `401` | `application/json` | `{\"message\": \"Requester is not the owner of the device\",\"statusCode\": 401}` |\n\u003e | `404` | `application/json` | `{\"message\": \"Device with id {{device_id}} was not found\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"User not found\",\"statusCode\": 404}` |\n\u003e | `404` | `application/json` | `{\"message\": \"No Data Found From The Given Period\",\"statusCode\": 404}` |\n\n##### Example cURL\n\n\u003e ```javascript\n\u003e curl --location 'http://localhost:3000/devices/1/temp/periodic?from=2024-10-18T11:03:28.273Z\u0026to=2024-10-18T11:05:28.273Z' \\\n\u003e --header 'Cookie: accessToken={{JWT_TOKEN}}' \\\n\u003e --header 'Content-Type: application/json'\n\u003e ```\n\n#### Example Response\n\n\u003e```javascript\n\u003e[\n\u003e    {\n\u003e        \"timestamp\": \"2024-10-18T11:05:25.896Z\",\n\u003e        \"value\": 1\n\u003e    },\n\u003e    {\n\u003e        \"timestamp\": \"2024-10-18T11:05:25.062Z\",\n\u003e        \"value\": 1\n\u003e    }\n\u003e]\n\u003e```\n\n\u003c/details\u003e\n\n----------------------------------------------","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupawite-peter%2Fsimple-iot-backend-microservices","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsupawite-peter%2Fsimple-iot-backend-microservices","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsupawite-peter%2Fsimple-iot-backend-microservices/lists"}