https://github.com/supawite-peter/simple-iot-backend-microservices
A simple IoT backend microservices based on nest.js framework
https://github.com/supawite-peter/simple-iot-backend-microservices
backend iot nestjs typescript
Last synced: 3 months ago
JSON representation
A simple IoT backend microservices based on nest.js framework
- Host: GitHub
- URL: https://github.com/supawite-peter/simple-iot-backend-microservices
- Owner: Supawite-Peter
- Created: 2024-11-23T05:40:21.000Z (7 months ago)
- Default Branch: master
- Last Pushed: 2024-12-18T14:09:44.000Z (7 months ago)
- Last Synced: 2025-03-21T00:44:21.884Z (3 months ago)
- Topics: backend, iot, nestjs, typescript
- Homepage:
- Size: 114 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Simple IoT Backend
## Description
A simple IoT backend server application based on the Nest.js framework. Serving user/device registration and database services through REST API.
## Concept
Users can register an IoT device with specific topics, each of which stores the sensor values transmitted from that device, as illustrated in this diagram.

Additionally, users have the flexibility to freely add or delete new devices and topics as needed.
Furthermore, users can query the latest data or data from a preferred time period.
## Planning Features
- :white_check_mark: Users Management Module
- :white_check_mark: Users Authentication Module
- :white_check_mark: Devices Management Module
- :white_check_mark: Devices Data Module
- :white_check_mark: MonogoDB implementation on Users/Devices Module
- :white_check_mark: Support Timestamp Devices Data
- :white_check_mark: API Docs
- :white_check_mark: Migrate User/Device/Topic to SQL DB
- :white_check_mark: Dockerize / Compose
- :white_check_mark: Caching
- :white_check_mark: JWT Refresh/Access Token
- :white_check_mark: JWT Cookie
- :white_check_mark: MQTT Support
- :black_square_button: API Key for Devices Data
- :black_square_button: Swagger UI Page
- :black_square_button: e2e Test
- :black_square_button: User Role
- :black_square_button: Aggregate Sensor Data
- :black_square_button: Simple Web Front-end
- ...## Run the project
1. Create and populate `.env` file. (example on `.env.example`)
3. Open `emqx.users.csv` and edit mqtt username and password as same as config in `.env` file
2. Run docker compose
```
docker-compose up -d
```## MQTT
This application allows sending sensor data to an MQTT broker using the topic format: `data/{deviceId}/{topic}`
### Access/Login to MQTT Broker
To access or log in to the MQTT broker:
1. Use the same username and password as registered in the `[POST]/users` endpoint.
2. 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.**Note**: Setting a separate MQTT password will disallow logging in to the MQTT broker with your base password.
### Payload Format
The payload must be a JSON object containing the following:
- `username`: Your registered username (string).
- `value`: The data value you want to send (number).### Example
To update the `temp` topic for a device with ID `4`, you can send:
```json
{
"username": "your_username",
"value": 5
}
```
Using the topic: `data/4/temp`
This will update the value `5` for the `temp` topic of device ID `4`.### Important Note
1. You can only update the value of a device ID and topic that you own or have registered.
2. The `username` in the payload is required to verify ownership of the device and topic.### Future Update
The requirement for including username in the payload will be removed in an upcoming update.## API
### Users
#### Add or Remove User Account
POST
/users
(Register new user account)
##### Authentication
> None
##### Parameters
> None
##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | username | required | string | string of username |
> | password | required | string | string of password |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `201` | `application/json` | `{"id": 1 ,"username": hello}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `409` | `application/json` | `{"message": "Username already exists","error": "Conflict","statusCode": 409}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/users' \
> --header 'Content-Type: application/json' \
> --data '{
> "username": "hello",
> "password": "world"
> }'
> ```
DELETE
/users
(Delete user account)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> None
##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `password` | required | string | string of password |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `{"id": 1, "username": "hello"}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "User does not exist","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location --request DELETE 'http://localhost:3000/users' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json' \
> --data '{
> "password": "world"
> }'
> ```#### Update User MQTT Password
PATCH
/users/mqtt/password
(Update user mqtt password)
##### Note
By default if mqtt password is not set, base password will be used for mqtt authentication instead.
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> None
##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | password | required | string | string of mqtt password |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `201` | `application/json` | `{"id": 1 ,"username": hello}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `404` | `application/json` | `{"message": "User is not exist", "statusCode": 404}` |##### Example cURL
> ```javascript
> curl --request PATCH
> --location 'http://localhost:3000/users/mqtt/password' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json' \
> --data '{
> "password": "world"
> }'
> ```#### Get User Detail
GET
/users/{userId}
(Get user account detail)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `userId` | required | number | targeted user id for details |##### Body
> None
##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `{"id": 1, "username": "hello"}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "User does not exist","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location --request GET 'http://localhost:3000/users/1' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json'
> ```---------------------------------------------------------
### Auth
#### Login
POST
/auth/login
(Login and set access token and refresh token cookies)
##### Authentication
> None
##### Parameters
> None
##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `username` | required | string | string of username |
> | `password` | required | string | string of password |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `None` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Incorrect password","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "User doesn't exist","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/auth/login' \
> --header 'Content-Type: application/json' \
> --data '{
> "username": "hello",
> "password": "world"
> }'
> ```#### Refresh new access token
GET
/auth/refresh
(set new access token cookie)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `refreshToken` | string | a Refresh JWT Token Set from `/auth/login` |##### Parameters
> None
##### Body
> None
##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `None` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Unauthorized", "statusCode": 401}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/auth/login' \
> --header 'Cookie: refreshToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json'
> ```------------------------------------------------------
### Devices
#### Add or Remove Device
POST
/devices
(Register new device to user)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> None
##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `name` | required | string | Name of the device |
> | `topics` | optional | string[] or string | Topics to be registered |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `201` | `application/json` | `{"id": 1, "name": "device1", "userId": 1, "topics": ["temp", "rh"]}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `400` | `application/json` | `{"message": "Device name is missing","statusCode": 400}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/devices' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json' \
> --data '{
> "name": "device1",
> "topics": ["temp","rh"]
>}'
> ```
DELETE
/devices
(Delete registered device)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> None
##### Body
> | name | type | data type | description |
> |-------|-------|-----------|-------------|
> | `id` | required | string or number | device id to be delete |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `{"id": 1, "name": "device2", "userId": 1, "topics": ["temp", "rh"]}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "Device with id {{deviceId}} was not found for user with id {{userId}}","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location --request DELETE 'http://localhost:3000/devices' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json' \
> --data '{
> "id": "1"
> }'
> ```#### Get Device Detail
GET
/devices/{deviceId}
(Get device details)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `deviceId` | required | number | targeted device id for details |##### Body
> None
##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `{"id": 1, "name": "device2", "userId": 1, "topics": ["temp", "rh"]}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "Device with id {{deviceId}} was not found for user with id {{userId}}","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location --request DELETE 'http://localhost:3000/devices/1' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json'
> ```#### List User Owned Devices
GET
/devices
(List every devices registered by current user)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> None
##### Body
> None
##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `[{"id": 1, "name": "device1", "userId": 1, "topics": ["temp", "rh"]}]` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "No devices found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/devices' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}'
> ```--------------------------------------------------------------------
#### Add or Remove Topics
POST
/devices/{deviceId}/topics
(Add new topics to a device)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `deviceId` | required | number | target device id to add topics |##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `topics` | required | string[] or string | topics to be added |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `201` | `application/json` | `{"topicsAdded": 1, "topics": ["air"]}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `400` | `application/json` | `{"message": "Topics are already registered","statusCode": 400}`|
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "Device with id {{deviceId}} was not found for user with id {{userId}}","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/devices/1/topics' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json' \
> --data '{
> "topics": "air"
>}'
> ```
DELETE
/devices/{deviceId}/topics
(Remove registered topic from a device)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `deviceId` | required | number | target device id to delete topics |##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `topics` | required | string or string[] | topics to be removed |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `201` | `application/json` | `{"topicsRemoved": 1, "topics": ["air"]}` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `400` | `application/json` | `{"message": "Topics are not registered","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "Device with id {{deviceId}} was not found for user with id {{userId}}","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location --request DELETE 'http://localhost:3000/devices/1/topics' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json' \
> --data '{
> "topics": "air"
> }'
> ```----------------------------------------------
#### Sending Sensor Data to a Topic
POST
/devices/{deviceId}/{topic}
(Sending Sensor Data to a Topic)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `deviceId` | required | number | target device id to storing data |
> | `topic` | required | string | target topic to storing data |##### Body
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `payload` | required | object or object[] | `{timestamp: {{iso_timestamp}}, value: {{number}}}` |##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `201` | `application/json` | `[{ "timestamp": {{iso_timestamp}},"value":{{number}} }, ...]` |
> | `400` | `application/json` | `{"message": "Validation failed","statusCode": 400}` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `401` | `application/json` | `{"message": "Requester is not the owner of the device","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "Device with id {{device_id}} was not found","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/devices/1/temp' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json' \
> --data '{
> "payload": [
> {
> "value": 0
> },
> {
> "timestamp": "2024-10-18T08:54:50.318Z",
> "value" : 3
> }
> ]
>}'
> ```#### Example Response
>```javascript
>[
> {
> "timestamp": "2024-10-18T10:57:26.776Z",
> "value": 0
> },
> {
> "timestamp": "2024-10-18T10:54:05.904Z",
> "value": 3
> }
>]
>```----------------------------------------------
#### Query Sensor Data
GET
/devices/{deviceId}/{topic}/latest
(Query latest data from a topic)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `deviceId` | required | number | target device id to storing data |
> | `topic` | required | string | target topic to storing data |##### Query Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `unix` | optional | boolean | true if want timestamp (ms) body to be in unix timestamp otherwise in iso timestamp (default) |##### Body
> None
##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `{ "timestamp": {{iso/unix_timestamp}},"value":{{number}} }` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `401` | `application/json` | `{"message": "Requester is not the owner of the device","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "Device with id {{device_id}} was not found","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "No Latest Data Found","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/devices/1/temp/latest' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}'
> ```#### Example Response
>```javascript
>{
> "timestamp": "2024-10-18T10:57:26.776Z",
> "value": 0
>}
>```
GET
/devices/{deviceId}/{topic}/periodic
(Query sensor data in between time)
##### Authentication
> | cookie key | type | description |
> |--------|------|-------------|
> | `accessToken` | string | a JWT Token Set from `/auth/login` or `/auth/refresh` |##### Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `deviceId` | required | number | target device id to storing data |
> | `topic` | required | string | target topic to storing data |##### Query Parameters
> | name | type | data type | description |
> |------|------|-----------|-------------|
> | `unix` | optional | boolean | true if want timestamp body to be in unix timestamp (ms) otherwise in iso timestamp (default) |
> | `from` | required | ISO String Datetime or Unix Timestamp (ms) | datetime indicating the starting point of requested data |
> | `to` | required | ISO String Datetime or Unix Timestamp (ms) | datetime indicating the end of requested data |##### Body
> None
##### Responses
> | http code | content-type | response |
> |-----------|--------------|----------|
> | `200` | `application/json` | `[{ "timestamp": {{iso/unix_timestamp}},"value":{{number}}, ...]}` |
> | `401` | `application/json` | `{"message": "Unauthorized","statusCode": 401}` |
> | `401` | `application/json` | `{"message": "Requester is not the owner of the device","statusCode": 401}` |
> | `404` | `application/json` | `{"message": "Device with id {{device_id}} was not found","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "User not found","statusCode": 404}` |
> | `404` | `application/json` | `{"message": "No Data Found From The Given Period","statusCode": 404}` |##### Example cURL
> ```javascript
> curl --location 'http://localhost:3000/devices/1/temp/periodic?from=2024-10-18T11:03:28.273Z&to=2024-10-18T11:05:28.273Z' \
> --header 'Cookie: accessToken={{JWT_TOKEN}}' \
> --header 'Content-Type: application/json'
> ```#### Example Response
>```javascript
>[
> {
> "timestamp": "2024-10-18T11:05:25.896Z",
> "value": 1
> },
> {
> "timestamp": "2024-10-18T11:05:25.062Z",
> "value": 1
> }
>]
>```----------------------------------------------