Real-time stock monitoring has become essential for investors who want to act quickly on market movements. Building a stocks price notifier application requires carefully orchestrating multiple technologies: a responsive frontend, a robust GraphQL backend, and efficient real-time data pipelines.
The combination of React, Apollo Client, and Hasura represents a powerful stack for building real-time applications. React provides a component-based architecture that makes building complex UIs manageable, with excellent performance characteristics through virtual DOM and efficient rendering. Apollo Client handles all aspects of GraphQL communication, including caching, optimistic UI updates, and subscription management for real-time features.
Hasura accelerates backend development by automatically generating a production-ready GraphQL API from your PostgreSQL database schema. This means you get type-safe queries, mutations, and subscriptions without writing boilerplate code. The modern stack delivers exceptional performance while keeping the codebase maintainable and scalable.
This guide walks through building a complete stocks price notifier system using React for the user interface, Apollo Client for GraphQL state management, and Hasura as the GraphQL engine powering the backend. Our web development services team regularly implements these patterns for real-time applications across various industries. The application will allow users to select stocks they're interested in, set price thresholds for notifications, and receive real-time alerts when those thresholds are crossed.
Each technology in this stack serves a specific purpose in building responsive real-time applications.
React Frontend
Component-based architecture with efficient rendering and hooks API for managing state and side effects.
Apollo Client
Comprehensive GraphQL state management with caching, optimistic updates, and subscription support.
Hasura GraphQL
Automatic GraphQL API generation from PostgreSQL with built-in real-time subscriptions and event triggers.
PostgreSQL + JSONB
Flexible schema design with JSONB columns for storing variable stock data structures.
Database Schema Design with PostgreSQL and JSONB
PostgreSQL's JSONB column type offers significant flexibility for storing stock-related data that may vary in structure. Stock APIs return different metrics depending on the data provider, and JSONB allows your schema to accommodate this variation without constant migrations. This approach keeps the schema flexible while still providing type-safe access patterns through Hasura's GraphQL API.
Our web development services include database architecture design that leverages PostgreSQL's advanced features for scalable applications. The JSONB capability is particularly valuable for applications handling diverse data sources or evolving requirements.
Core Tables
The stocks table stores basic information about each tracked stock. Using JSONB for the metadata column allows you to capture additional metrics like dividend yield, market cap, or sector information without altering the schema.
The price_history table captures historical price data for time-series analysis and trend visualization. Each entry includes open, high, low, close prices, and volume, with a JSONB column for capturing any additional raw data from the data provider.
The price_alerts table stores user-defined price thresholds. When stock prices cross these thresholds, the application triggers notifications. Each alert references the user who created it, the stock being monitored, the target price, and whether the condition is for prices going above or below the target.
CREATE TABLE stocks (
id SERIAL PRIMARY KEY,
symbol VARCHAR(10) UNIQUE NOT NULL,
company_name TEXT NOT NULL,
exchange VARCHAR(50),
metadata JSONB DEFAULT '{}'
);
CREATE TABLE price_history (
id SERIAL PRIMARY KEY,
stock_id INTEGER REFERENCES stocks(id),
recorded_at TIMESTAMP WITH TIME ZONE NOT NULL,
open_price DECIMAL(10, 2),
high_price DECIMAL(10, 2),
low_price DECIMAL(10, 2),
close_price DECIMAL(10, 2) NOT NULL,
volume BIGINT,
raw_data JSONB DEFAULT '{}'
);
CREATE TABLE price_alerts (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
stock_id INTEGER REFERENCES stocks(id),
target_price DECIMAL(10, 2) NOT NULL,
condition_type VARCHAR(10) CHECK (condition_type IN ('ABOVE', 'BELOW')),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
JSONB Query Capabilities
Hasura GraphQL exposes JSONB operators that enable sophisticated filtering without complex joins. The _contains operator allows filtering stocks based on specific metadata criteria, such as finding all stocks in a particular sector. The _append and _prepend operators enable array manipulation in JSONB columns, while _delete_key and _delete_at_path support removing specific fields. These operators enable powerful querying patterns that would otherwise require complex joins or multiple queries, keeping your application responsive and your queries efficient.
Setting Up Hasura GraphQL Engine
Hasura dramatically simplifies backend development by automatically generating a complete GraphQL API from your PostgreSQL schema. After connecting Hasura to your PostgreSQL database, it introspects the schema and creates queries, mutations, and subscriptions for each table. This immediate API availability means you can start building your frontend without waiting for backend development.
Automatic Schema Generation
Start Hasura with your PostgreSQL connection string. The GraphQL engine immediately provides a ready-to-use API with introspection support. The console offers a visual interface for exploring your schema and testing queries. Track the tables you want to expose through the GraphQL API--for the stocks application, track the stocks, price_history, and price_alerts tables. Hasura generates appropriate GraphQL types and operations for each automatically.
Permissions ensure users can only access their own alerts and view publicly available stock information. Row-level security rules on the price_alerts table ensure users only see alerts they've created, protecting user data while maintaining a seamless experience.
Real-time Subscriptions
Hasura's subscription support enables real-time updates when underlying data changes. For stock prices, this means clients receive updates immediately when new price data arrives. Enable subscriptions on tracked tables through the console or metadata configuration. Each table that needs real-time updates should have subscriptions enabled. Test subscriptions through GraphiQL to verify real-time functionality before integrating with the frontend.
Event Triggers for Price Alerts
Hasura event triggers invoke webhooks when database events occur. For the stocks application, configure triggers on the price_history table to evaluate price alerts. When new price data is inserted, the trigger evaluates whether any active alerts should fire.
The webhook receives the new price data, queries active alerts for that stock, evaluates each alert's condition against the new price, sends notifications for matching conditions, and marks fired alerts as inactive if configured for one-time notification. This event-driven architecture keeps the notification system responsive without requiring constant polling--the trigger fires immediately when price data arrives, ensuring timely notifications without unnecessary resource consumption.
Integrating Apollo Client with React
Apollo Client provides a unified interface for all GraphQL operations in your React application. Its hooks API makes it easy to integrate queries, mutations, and subscriptions into functional components. For subscriptions, add a WebSocket link to handle real-time connections. The split link pattern routes queries and mutations over HTTP while subscriptions use WebSocket, optimizing network usage.
useQuery for Data Fetching
The useQuery hook fetches data and manages loading and error states. For displaying stock information, use queries with appropriate fragments. Apollo's cache automatically normalizes query results, ensuring consistency across components and reducing unnecessary network requests. Components referencing the same data receive updates from the cache automatically.
import { useQuery, gql } from '@apollo/client';
const GET_STOCKS = gql`
query GetStocks {
stocks {
id
symbol
company_name
metadata
}
}
`;
function StockList() {
const { loading, error, data } = useQuery(GET_STOCKS);
if (loading) return <p>Loading stocks...</p>;
if (error) return <p>Error loading stocks: {error.message}</p>;
return (
<ul>
{data.stocks.map(stock => (
<li key={stock.id}>{stock.symbol} - {stock.company_name}</li>
))}
</ul>
);
}
useSubscription for Real-time Updates
The useSubscription hook sets up real-time data streams. For live price updates, subscribe to new entries in the price_history table. The subscription automatically reconnects if the WebSocket connection drops, and Apollo handles all reconnection logic transparently. This pattern provides sub-second update propagation through GraphQL subscriptions, essential for responsive stock monitoring.
import { useSubscription, gql } from '@apollo/client';
const PRICE_SUBSCRIPTION = gql`
subscription OnPriceUpdate($stockId: Int!) {
price_history(where: { stock_id: { _eq: $stockId } }, limit: 1, order_by: { recorded_at: desc }) {
id
recorded_at
close_price
volume
}
}
`;
function LivePrice({ stockId }) {
const { data, loading } = useSubscription(PRICE_SUBSCRIPTION, {
variables: { stockId },
});
if (loading) return <span>Connecting...</span>;
if (!data || data.price_history.length === 0) return <span>No data</span>;
const latestPrice = data.price_history[0];
return (
<div className="price-display">
<span className="price">${latestPrice.close_price}</span>
<span className="time">
{new Date(latestPrice.recorded_at).toLocaleTimeString()}
</span>
</div>
);
}
Managing Price Alerts and Notifications
Price alerts bridge user intent with real-time market data. When users create alerts, the system must track them and evaluate conditions efficiently as new prices arrive. The architecture ensures immediate notification delivery without requiring constant polling, providing a responsive user experience.
For automated notification systems, our AI automation services can help integrate machine learning for predictive alerting and advanced notification routing across multiple channels.
Creating Price Alerts
Users create alerts through a form specifying the stock, target price, and condition (above or below). Mutations submit this data to the database, and the new alert immediately becomes active for monitoring. The alert creation flow should provide immediate feedback while the backend processes the subscription.
const CREATE_ALERT = gql`
mutation CreateAlert($stockId: Int!, $targetPrice: numeric!, $condition: String!) {
insert_price_alerts_one(object: {
stock_id: $stockId,
target_price: $targetPrice,
condition_type: $condition,
user_id: "CURRENT_USER_ID"
}) {
id
target_price
condition_type
}
}
`;
function AlertForm({ stockId, onAlertCreated }) {
const [createAlert] = useMutation(CREATE_ALERT);
const [price, setPrice] = useState('');
const [condition, setCondition] = useState('ABOVE');
const handleSubmit = async () => {
await createAlert({
variables: {
stockId,
targetPrice: parseFloat(price),
condition,
},
});
onAlertCreated();
setPrice('');
};
return (
<div className="alert-form">
<select value={condition} onChange={e => setCondition(e.target.value)}>
<option value="ABOVE">Price goes above</option>
<option value="BELOW">Price goes below</option>
</select>
<input
type="number"
value={price}
onChange={e => setPrice(e.target.value)}
placeholder="Target price"
/>
<button onClick={handleSubmit}>Create Alert</button>
</div>
);
}
Trigger Evaluation Architecture
Hasura event triggers evaluate price alerts when new data arrives. The trigger invokes a webhook that receives the new price data, queries active alerts for that stock, evaluates each alert's condition against the new price, sends notifications for matching conditions, and marks fired alerts as inactive if configured for one-time notification. This event-driven approach eliminates polling and ensures alerts fire immediately when conditions are met. The webhook can be deployed as a serverless function, scaling automatically with notification volume.
Notification Delivery
Notifications may use various channels depending on user preferences and application requirements. In-app notifications display through the React interface using a dedicated notifications table that subscriptions can observe. Push notifications reach mobile devices through platform-specific services. Integration with messaging platforms like Slack or Discord provides alerts in tools users already use frequently.
For in-app notifications, when an alert fires, insert a notification record that the user's components can subscribe to. A GraphQL subscription on the notifications table creates a seamless notification experience without requiring users to refresh the page.
Performance Optimization Strategies
Building a responsive stock monitoring application requires attention to performance at every layer. Network efficiency, rendering optimization, and database queries all impact user experience. Profiling each layer helps identify bottlenecks before they affect users. Our web development services team specializes in performance optimization for real-time applications.
Apollo Client Caching
Apollo's normalized cache stores GraphQL responses by identity, not by query. This means if the same stock appears in multiple queries, it's cached as a single object. Updates to that stock propagate to all components referencing it. Configure cache behavior for your specific needs--for frequently changing price data, consider shorter cache time-to-live or manual cache eviction after mutations. Custom type policies optimize cache behavior for your specific data model.
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache({
typePolicies: {
PriceHistory: {
keyFields: ['id'],
},
Stocks: {
keyFields: ['symbol'],
},
},
}),
});
Subscription Connection Management
WebSocket connections for subscriptions consume server resources. Optimize by subscribing only to stocks the user is actively viewing, using scoped subscriptions that filter by stock ID rather than fetching all prices, and leveraging useSubscription's automatic cleanup when components unmount. For users monitoring many stocks, consider aggregating updates into periodic refreshes rather than individual subscriptions to reduce connection overhead.
Database Query Optimization
Index foreign keys and frequently filtered columns for fast queries. For price history queries, an index on (stock_id, recorded_at) supports both filtering by stock and time-range queries efficiently. Use PostgreSQL's automatic query optimization through proper table statistics--regular ANALYZE operations keep the query planner informed about data distributions.
CREATE INDEX idx_price_history_stock_time
ON price_history(stock_id, recorded_at DESC);
React Rendering Optimization
React components only re-render when their props or state change. Use React.memo for components that receive the same props repeatedly, preventing unnecessary re-renders when parent components update. For large stock lists, implement virtualization with libraries like react-window, which renders only visible items and reduces DOM node count for improved scroll performance on large datasets.
Best Practices for Production Deployment
Production deployments require additional considerations beyond development functionality. Robust error handling, secure authentication, and operational monitoring ensure reliable service that users can depend on.
Error Handling Patterns
Network errors, GraphQL validation errors, and server errors require different handling strategies. Apollo Client provides error boundaries and network status tracking. Implement retry logic for transient failures--Apollo's retryLink automatically retries failed requests with exponential backoff, improving resilience without manual intervention.
Authentication Integration
Secure the GraphQL endpoint with authentication tokens. Hasura supports JWT mode, validating tokens and extracting user context for permission rules. Session variables from JWT claims power row-level security rules, ensuring users only access their own alerts while seeing public stock information.
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('auth_token');
return {
headers: {
...headers,
Authorization: token ? `Bearer ${token}` : '',
}
};
});
Monitoring and Observability
Track key metrics: subscription connection count, query response times, and alert trigger latency. Hasura console provides built-in monitoring, or integrate with external observability platforms for production deployments. Log significant events: alert creations, notification deliveries, and any errors. Structured logging with correlation IDs helps trace issues through the system during troubleshooting.
Health checks verify database connectivity and WebSocket functionality. Deploy monitoring dashboards that alert operations teams to issues before users notice them. Track trends over time to identify capacity needs before they become urgent.
Conclusion
Building a stocks price notifier with React, Apollo Client, and Hasura demonstrates how modern web technologies combine to create responsive, real-time applications. React's component model structures the UI, Apollo Client manages GraphQL communication with intelligent caching, and Hasura provides a scalable backend without custom server code. The architecture scales naturally: PostgreSQL handles data storage, Hasura manages the GraphQL API and subscriptions, and React delivers a fluid user experience.
JSONB columns provide schema flexibility for evolving data requirements, while GraphQL subscriptions deliver real-time updates with minimal overhead. This stack works well for similar applications--cryptocurrency trackers, commodity monitors, or any domain requiring real-time data updates. The patterns established here transfer directly to those use cases.
Next Steps
Consider extending the application with charting libraries like Recharts or Victory for price visualization, machine learning predictions based on historical patterns, or integration with additional data providers. Explore implementing portfolio tracking features or market comparison tools. The foundation of React, Apollo, and Hasura supports these enhancements while maintaining clean architecture that's easy to extend.
For organizations looking to build similar real-time monitoring applications, our web development services can help architect and implement custom solutions tailored to your specific requirements. Additionally, our AI automation services team can assist with predictive analytics and intelligent notification systems for advanced use cases.
Frequently Asked Questions
What makes GraphQL subscriptions better than polling for stock prices?
GraphQL subscriptions establish a persistent WebSocket connection, pushing updates immediately when data changes. Polling requires repeated HTTP requests at intervals, wasting bandwidth and introducing latency. Subscriptions deliver sub-second updates with minimal server load.
How does Hasura handle authentication for subscriptions?
Hasura validates JWT tokens during the WebSocket handshake, extracting user context from session variables. These variables power row-level security rules, ensuring users only access their own alerts while viewing public stock information.
Can I use this architecture for cryptocurrency tracking?
Absolutely. The same patterns apply--swap stock data sources for cryptocurrency APIs, adjust the schema for crypto-specific metrics, and the real-time subscription architecture handles high-frequency updates efficiently.
What happens if the WebSocket connection drops?
Apollo Client automatically attempts reconnection with exponential backoff. For critical applications, implement connection state monitoring and display reconnection status to users. The cache maintains data during brief disconnections.
How do I scale this for enterprise use?
Hasura supports horizontal scaling through read replicas for queries and subscriptions. Consider read-replica routing for read-heavy workloads, database connection pooling, and CDN caching for static metadata. Kubernetes orchestration handles pod scaling based on load.
What JSONB operators does Hasura support?
Hasura exposes PostgreSQL JSONB operators including _contains for object matching, _append and _prepend for concatenation, _delete_key for removing keys, _delete_elem for array elements, and _delete_at_path for nested deletion. These enable sophisticated filtering without complex joins.