A Ruby on Rails API for tracking sleep patterns and following other users' sleep records.
- User authentication with authentication-zero
- Create and manage sleep records
- Follow/unfollow other users
- View sleep records of users you follow
- View sleep records sorted by duration
- Ruby 3.4.1
- PostgreSQL 14+
- Docker and Docker Compose (optional, for containerized setup)
-
Clone the repository
git clone https://github.com/residwi/sleep-tracker-api.git cd sleep-tracker-api
-
Install dependencies
bundle install
-
Setup the database
rails db:prepare
-
Start the server
rails server
You can also run the application using Docker Compose, which sets up both the Rails application and PostgreSQL database in containers.
-
Clone the repository
git clone https://github.com/residwi/sleep-tracker-api.git cd sleep-tracker-api
-
Build and start the containers
docker compose up --build app
-
Access the application at http://localhost:3000
-
Running commands inside the container
# Run Rails commands docker compose exec app rails c # Run tests docker compose exec app bundle exec rspec
Run the test suite with:
bundle exec rspec
The Sleep Tracker API uses a relational database with the following schema:
POST /api/v1/sign_in
- Sign in with email and passwordDELETE /api/v1/sessions/:id
- Sign out
GET /api/v1/sleep_records
- List current user's sleep recordsPOST /api/v1/sleep_records
- Create a new sleep recordGET /api/v1/sleep_records/:id
- Get a specific sleep recordPUT /api/v1/sleep_records/:id
- Update a sleep recordDELETE /api/v1/sleep_records/:id
- Delete a sleep record
GET /api/v1/users
- List all usersGET /api/v1/users/:id
- Get user profileGET /api/v1/users/:id/sleep_records
- Get a user's sleep recordsGET /api/v1/users/:id/followers
- Get a user's followersGET /api/v1/users/:id/following
- Get a user's followingPOST /api/v1/users/:id/follow
- Follow a userDELETE /api/v1/users/:id/unfollow
- Unfollow a user
GET /api/v1/feeds
- Get sleep records of followed users from the previous week, sorted by duration
Endpoint: POST /api/v1/sign_in
Authentication: Not required
Request Body:
Field | Type | Required | Description | Validation |
---|---|---|---|---|
String | Yes | User's email address | Must be a valid email format | |
password | String | Yes | User's password | Must match the stored password |
Example Request Body:
{
"email": "[email protected]",
"password": "securepassword123"
}
Endpoint: DELETE /api/v1/sessions/:id
Authentication: Required
URL Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
id | Integer | Yes | The ID of the session to destroy |
Endpoint: POST /api/v1/sleep_records
Authentication: Required
Request Body:
Field | Type | Required | Description | Validation |
---|---|---|---|---|
start_time | DateTime | Yes | When the sleep period started | Must be a valid ISO 8601 datetime |
end_time | DateTime | No | When the sleep period ended | Must be later than start_time if provided |
Example Request Body:
{
"start_time": "2025-05-07T22:30:00Z",
"end_time": "2025-05-08T06:45:00Z"
}
Endpoint: PUT /api/v1/sleep_records/:id
Authentication: Required
URL Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
id | Integer | Yes | The ID of the sleep record to update |
Request Body:
Field | Type | Required | Description | Validation |
---|---|---|---|---|
start_time | DateTime | No | When the sleep period started | Must be a valid ISO 8601 datetime |
end_time | DateTime | No | When the sleep period ended | Must be later than start_time if provided |
Example Request Body:
{
"end_time": "2025-05-08T07:15:00Z"
}
Endpoint: POST /api/v1/users/:id/follow
Authentication: Required
URL Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
id | Integer | Yes | The ID of the user to follow |
Request Body: None required
Endpoint: DELETE /api/v1/users/:id/unfollow
Authentication: Required
URL Parameters:
Parameter | Type | Required | Description |
---|---|---|---|
id | Integer | Yes | The ID of the user to unfollow |
Request Body: None required
The Sleep Tracker API includes several performance optimizations to handle a growing user base and high data volumes:
-
Eager Loading
I use eager loading to prevent N+1 query problems:
SleepRecord.where(user_id: following_ids).includes(:user)
-
Selective Column Fetching
Only necessary columns are fetched to reduce memory usage:
SleepRecord.select(:id, :start_time, :end_time, :duration, :user_id)
The API uses keyset pagination for efficient handling of large datasets:
@pagy, @records = pagy_keyset(@sleep_records, items: 20)
render json: {
data: @records,
pagination: {
next: pagy_url_for(@pagy, @pagy.next, absolute: true)
}
}
Keyset pagination offers several advantages over traditional offset-based pagination:
- Consistent performance regardless of page depth
- No issues with records shifting during pagination
- Better performance with large datasets
While Redis is a powerful tool for caching and can significantly improve performance, I have made a deliberate decision to delay its implementation initially. Because:
-
Measurement First Approach
- I prioritize measuring actual performance bottlenecks before implementing solutions
- This prevents premature optimization and unnecessary complexity
-
Cost Considerations
- Redis adds operational costs (hosting, maintenance, monitoring)
- For new applications, it's important to validate the need before incurring these costs
-
Current Optimization Alternatives
- Database indexing strategy
- Query optimization techniques
- Selective column fetching
- Keyset pagination
- These optimizations often provide significant performance improvements without additional infrastructure
-
Implementation Criteria
- Redis will be considered when:
- Response times consistently exceed performance targets
- Database load becomes a bottleneck
- Specific endpoints show high cache hit potential
- User base grows beyond what database-only optimizations can handle
- Redis will be considered when: