Live Demo ย โขย Swagger Documentation ย โขย Frontend Repository
BidX modernizes traditional auction processes by enabling real-time digital bidding. Users can participate in live auctions, place bids, track updates instantly, and interact securely with auction winners or auctioneersโall while receiving immediate notifications for critical actions like bid acceptance or outbidding.
- Real-Time Bidding: Place and track bids with live price updates. Auctioneer can accept bids instantly.
- Real-Time Chat: Chat is restricted to auction winners and auctioneers, with read receipts and user status indicators.
- Real-Time Notifications: Bidders get alerts for accepted or outbid bids; auctioneers are notified of new bids.
- Real-Time Auctions Feed: Live updates for new, deleted, or ended auctions, and price changes. Includes filtering and search.
- Reviews: Reviews are allowed only between auction winners and auctioneers.
- Authentication: Supports traditional email/password authentication and Google authentication.
- ASP.NET Core 9 - A free, cross-platform and open-source web-development framework.
- Entity Framework Core 9 - An open source objectโrelational mapping framework.
- Microsoft SQL Server - A relational database management system.
- SignalR - A library that enables real-time communication between servers and clients.
- ASP.NET Core Identity - A membership system for managing users, authentication, and authorization in ASP.NET Core applications.
- JWT - A secure, compact token format used for transmitting information between parties as a JSON object.
- Serilog - A logging library that allows logging to various outputs like files, console, etc.
- MediatR - A simple, unambitious mediator implementation in .NET for in-process messaging.
- Quartz.NET - An open source job scheduling system for .NET.
- Docker - A containerization platform for packaging applications and their dependencies.
- Google Identity Services (OpenID Connect) - An authentication solution enabling secure sign-in with Google using OpenID Connect and OAuth 2.0.
- Cloudinary - A cloud-based service for file storage and image management.
- Brevo - An email sending service.
Check Swagger Documentation for full endpoint details.
- Client authenticates with Google to get an
idToken
. idToken
is sent to the BidX API.- API returns
userInfo
+accessToken
, and sets arefreshToken
in an HTTP-only cookie.
- Client sends email/password to BidX API.
- API returns
userInfo
+accessToken
, and sets arefreshToken
in an HTTP-only cookie.
- Client sends request to
/refresh
endpoint with the HTTP-onlyrefreshToken
cookie. - API returns new
userInfo
+accessToken
.
Note
Refresh tokens returned in the response body for non-browser clients (e.g., mobile apps).
A standardized error response is returned if an error occurs.
{
"errorCode": "AUTH_EMAIL_NOT_CONFIRMED",
"errorMessages": ["The email has not been confirmed"]
}
Here are the available error codes.
- BidX uses a room-based Pub-Sub model:
- Why? To enhance the performance and prevent clients from being flooded with irrelevant events.
- The connection must be restarted when logging in or logging out:
- Why? To avoid an authorized connection being used by an unauthorized client or vice versa.
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${import.meta.env.VITE_BIDX_API_URL}/hub`, {
accessTokenFactory: () => accessToken,
transport: signalR.HttpTransportType.WebSockets,
skipNegotiation: true // Skip the negotiate request and establish the WS connection directly
})
.withServerTimeout(10000) // 2X the KeepAliveInterval value configured by the server
.withKeepAliveInterval(5000) // 0.5X the ClientTimeoutInterval value configured by the server
.withAutomaticReconnect()
.build();
// Start connection
try {
await connection.start();
} catch (err) {
console.log(err);
}
// Invoke server method
connection.invoke("PlaceBid", { auctionId: "123", amount: 500 });
// Listen for server event
connection.invoke("JoinAuctionRoom"); // Subscribe to receive updates.
connection.on("BidPlaced", (response) => {
console.log("New bid placed:", response);
});
connection.invoke("LeaveAuctionRoom"); // Unsubscribe to stop receiving updates.
Method | Parameters | Description |
---|---|---|
PlaceBid |
BidRequest |
Submit a bid for an auction. |
AcceptBid |
AcceptBidRequest |
Auctioneer accepts a bid. |
JoinAuctionRoom |
JoinAuctionRoomRequest |
Subscribe to auction updates. |
LeaveAuctionRoom |
LeaveAuctionRoomRequest |
Unsubscribe from auction updates. |
Event | Parameters | Audience |
---|---|---|
BidPlaced |
BidResponse |
Clients in auction room |
BidAccepted |
BidResponse |
Clients in auction room |
Method | Parameters | Description |
---|---|---|
JoinFeedRoom |
None | Subscribe to feed updates. |
LeaveFeedRoom |
None | Unsubscribe from updates. |
Event | Parameters | Audience |
---|---|---|
AuctionCreated |
AuctionResponse |
Feed room clients |
AuctionDeleted |
AuctionDeletedResponse |
Feed room clients |
AuctionEnded |
AuctionEndedResponse |
Feed room clients |
AuctionPriceUpdated |
AuctionPriceUpdatedResponse |
Feed room clients |
Method | Parameters | Description |
---|---|---|
SendMessage |
SendMessageRequest |
Send a chat message. |
MarkMessageAsRead |
MarkMessageAsReadRequest |
Mark a message as read. |
MarkAllMessagesAsRead |
MarkAllMessagesAsReadRequest |
Mark all messages received as read. |
JoinChatRoom |
JoinChatRoomRequest |
Subscribe to chat updates. |
LeaveChatRoom |
LeaveChatRoomRequest |
Unsubscribe from updates. |
Event | Parameters | Audience |
---|---|---|
MessageReceived |
MessageResponse |
Chat room clients |
MessageRead |
MessageReadResponse |
Chat room clients |
AllMessagesRead |
AllMessagesReadResponse |
Chat room clients |
UserStatusChanged |
UserStatusResponse |
Chat room clients |
UnreadChatsCountUpdated |
UnreadChatsCountResponse |
Affected user |
Method | Parameters | Description |
---|---|---|
MarkNotificationAsRead |
MarkNotificationAsReadRequest |
Mark a notification as read. |
MarkAllNotificationsAsRead |
None | Mark all notifications received as read. |
Event | Parameters | Audience |
---|---|---|
UnreadNotificationsCountUpdated |
UnreadNotificationsCountResponse |
Affected user |
A standardized error response is returned if an error occur.
{
"errorCode": "BIDDING_NOT_ALLOWED",
"errorMessages": ["Auction has ended"]
}
Errors triggered via ErrorOccurred
event.
Event | Parameters | Audience |
---|---|---|
ErrorOccurred |
ErrorResponse |
Caller client only |
Although I'm using the EF Core Code First approach (where the database is generated from the C# entity classes), I still prefer to start any project by designing the database and normalize it.
The database is optimized for performance through strategic denormalization and Covering indexes:
- Denormalized Columns:
Chat.LastMessageId
: Added to avoid expensive joins when fetching chats with their latest message.User.AverageRating
: Added to avoid expensive joins and calculations when fetching the users with their average rating.
- Triggers: Ensure denormalized columns stay synchronized with source data (e.g., updating
LastMessageId
when a message is added).- See the
SQL/
folder for triggers definitions.
- See the
- Covering Indexes: Applied to frequently queried columns to avoid full table scans.
- See the
Configs/
folder for index definitions.
- See the
Note
Some auth-related tables have been omitted from the diagram for simplicity and clarity.
Here are some challenges I encountered during development and the solutions I implemented to overcome them:
-
- Challenge: Refresh tokens needed to be returned as HTTP-only cookies for browser clients (for security) and in the response body for non-browser clients (e.g., mobile apps).
- Solution: Used the
User-Agent
HTTP header to detect the client type and conditionally set the token delivery method.
-
- Challenge: Modern browsers disable third-party cookies by default or let users opt out to protect privacy. This broke token refreshes because the frontend (e.g.,
bidx-app.com
) and backend (bidx-api.com
) were on separate domains. - Solution: Adopted CHIPS (Partitioned Cookies).
- How It Works:
- The backend sets a
refreshToken
cookie with thePartitioned
attribute. - The cookie is stored in a partitioned space, tied strictly to the top-level domain (
bidx-app.com
). - Itโs sent only when the frontend explicitly calls the backend, avoiding cross-site tracking risks.
- The backend sets a
- How It Works:
- Challenge: Modern browsers disable third-party cookies by default or let users opt out to protect privacy. This broke token refreshes because the frontend (e.g.,
-
- Challenge: Managing auction start and end times across different time zones without relying on user-local time inputs, which can lead to ambiguity due to regional offsets and daylight saving rules.
- Solution:
- Duration-Based Scheduling: Users specify auction duration in seconds (e.g., 86400 seconds for 24 hours).
- UTC Anchoring: Calculate and store the start time as
DateTimeOffset.UtcNow
and derive the end time by adding the duration to this UTC anchor. - Frontend Localization: Display times in the userโs local time zone while processing all timestamps in UTC.
-
- Challenge: The auctions feed page (and other pages with auction lists) had slow loading times because the original uploaded images were used as thumbnails, which were large in size.
- Solution: Created a separate, compressed, lower-quality version of the image using Cloudinary to be set as a thumbnail for auctions and serve them for feed page and serve full-resolution images for auction details page only.
- Install Docker and Docker Compose.
- Clone the repository:
git clone https://github.com/Youssef-Adell/BidX-API.git
cd BidX-API
- Rename the example files:
webapi.env.example
โwebapi.env
sqlserver.env.example
โsqlserver.env
- Update the
.env
files with your credentials:webapi.env
: Add database connection strings, JWT secret, and third-party API keys.sqlserver.env
: Set the SQL Server admin password.
3. Edit appsettings.json
Update the following attributes in appsettings.json
as needed:
"BrevoEmailApi": {
"ConfirmationEmailTemplateId": "3",
"PasswordResetEmailTemplateId": "5"
},
"Cors": {
"FrontendOrigin": "http://localhost:3000"
},
"AuthPages": {
"EmailConfirmationPageUrl": "http://localhost:3000/confirm-email",
"ResetPasswordPageUrl": "http://localhost:3000/reset-password"
}
Run the following command to build and start the containers:
docker-compose up --build
- The API will be available at http://localhost:5000.
- SQL Server will be accessible at localhost:1433.
Note
Database, logs, and DataProtection keys are stored in Docker volumes (sqlserver-data, webapi-logs, dataprotection-keys) to ensure data persistence and consistency across container restarts or rebuilds.
-
- Implement mechanisms to detect and block spam bids, ensuring fair auctions.
-
- Add automated checks to block inappropriate or harmful images uploaded by users.
-
- Apply rate limits to critical endpoints to prevent abuse and ensure system stability.