Reliable access to financial data is not just important—it's mission-critical. While many companies start with a single data aggregator, this approach can lead to significant challenges:
This is where a multi-aggregator strategy comes in. By leveraging Quiltt's unified API, you can implement a robust multi-aggregator approach that provides better coverage, enhanced reliability, and an improved user experience. Let's walk through how to build this strategy step by step.
Before we dive in, you'll need:
Don't worry if you're not an expert in all these areas—we'll explain each concept as we go.
This tutorial uses pnpm
as the package manager and Next.js as the web framework. However, the core concepts and integration patterns remain framework-agnostic - you can use any package manager (npm
, yarn
, bun
) or any web framework of your choice. The key Quiltt integration concepts will remain the same.
Before implementing any code, it's crucial to understand the aggregators at your disposal. Quiltt currently supports:
Understanding these differences will inform your implementation strategy.
Think of your Quiltt environment as the command center for your multi-aggregator strategy. Here's how to set it up:
pnpm create next-app my-quiltt-app --typescript --tailwind --appcd my-quiltt-app
pnpm add @quiltt/react
# .env.localNEXT_PUBLIC_QUILTT_CONNECTOR_ID=your_connector_id_hereQUILTT_API_KEY=your_api_key_here
The Quiltt React SDK requires a valid JWT token to authenticate API requests. When usinguseQuery
anduseSubscription
, obtain a Session token using a server-side API call and pass it using theQuilttProvider
component oruseQuilttSession
hook.
For detailed instructions on handling Authentication, see our Authentication Tutorial.
// src/app/layout.tsx
import { QuilttProvider } from '@quiltt/react'
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<QuilttProvider>
{children}
</QuilttProvider>
</body>
</html>
)
}
Before writing any code, you'll need to configure your connector in the Quiltt Dashboard. This will determine which aggregators and features are available to your users.
Authentication
configurationEnrollment
configurationConnect
section:ExitRedirect
section to:Redirect URL
to http://localhost:3000/login
Send Token
Let's implement the core connection components:
The ConnectAccounts
component provides a general-purpose button that launches the Quiltt connector dialog, allowing users to connect any supported financial institution:
// src/components/ConnectAccounts.tsx
'use client'
import { QuilttButton } from '@quiltt/react'
import type { ConnectorSDKOnExitSuccessCallback, ConnectorSDKOnLoadCallback } from '@quiltt/react'
export default function ConnectAccounts() {
const handleLoad: ConnectorSDKOnLoadCallback = (metadata) => {
console.log(`Connector ${metadata.connectorId} loaded!`)
}
const handleSuccess: ConnectorSDKOnExitSuccessCallback = (metadata) => {
console.log('Connection established:', metadata.connectionId)
}
return (
<QuilttButton
connectorId={process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID!}
onLoad={handleLoad}
onExitSuccess={handleSuccess}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Connect Account
</QuilttButton>
)
}
The PrefilledConnector
component demonstrates how to pre-select a specific institution (Chase in this example) for a more streamlined connection experience:
// src/components/PrefilledConnector.tsx
'use client'
import { QuilttButton } from '@quiltt/react'
export default function PrefilledConnector() {
return (
<QuilttButton
connectorId={process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID!}
institution="Chase"
onExitSuccess={(metadata) => {
console.log('Connection established:', metadata.connectionId)
}}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Connect Chase Account
</QuilttButton>
)
}
After a successful connection, the Quiltt Connector redirects users to your login page with an authentication token. Let's implement the login handler:
/ src/app/login/page.tsx
'use client'
import { useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { useQuilttSession } from '@quiltt/react'
export default function LoginPage() {
const router = useRouter()
const searchParams = useSearchParams()
const { importSession } = useQuilttSession()
useEffect(() => {
const token = searchParams.get('token')
if (!token) {
console.error('No authentication token found')
return
}
const processToken = async () => {
try {
const success = await importSession(token)
if (success) {
router.push('/connections')
} else {
console.error('Failed to import session')
}
} catch (error) {
console.error('Error processing authentication token:', error)
}
}
processToken()
}, [importSession, router, searchParams])
return (
<main className="flex min-h-screen items-center justify-center">
<div className="rounded-lg bg-white p-8 shadow-lg">
<h1 className="mb-4 font-bold text-2xl">Processing Authentication...</h1>
<p className="text-gray-600">Please wait while we complete your login.</p>
</div>
</main>
)
}
After implementing this login page:
http://localhost:3000/login?token={authToken}
useQuilttSession
processes and stores the token/connections
This completes the connection flow and prepares us for accessing financial data in the next section.
The journey from initiating a connection to accessing financial data involves several key steps. Let's break this down into a clear process.
The Quiltt Connector SDK provides a comprehensive callback system that lets you track every step of the connection process. These events are crucial for implementing a robust multi-aggregator strategy, as they allow you to:
Here's a robust implementation of a connection manager:
// src/components/ConnectionManager.tsx
'use client'
import { useState } from 'react'
import { QuilttButton, ConnectorSDKEventType } from '@quiltt/react'
import type { ConnectorSDKCallbackMetadata } from '@quiltt/react'
interface ConnectionManagerProps {
onConnectionEstablished?: (connectionId: string) => void
}
export default function ConnectionManager({ onConnectionEstablished }: ConnectionManagerProps) {
const [connectionState, setConnectionState] = useState<{
status: 'idle' | 'connecting' | 'connected' | 'error'
message?: string
}>({ status: 'idle' })
// Comprehensive event handler for all connector events
const handleEvent = (type: ConnectorSDKEventType, metadata: ConnectorSDKCallbackMetadata) => {
console.log(`Connector event: ${type}`, metadata)
switch (type) {
case ConnectorSDKEventType.Open:
setConnectionState({
status: 'connecting',
message: 'Connector opened',
})
break
case ConnectorSDKEventType.Load:
setConnectionState({
status: 'connecting',
message: `Connector ${metadata.connectorId} loaded`,
})
break
case ConnectorSDKEventType.ExitSuccess:
if (metadata.connectionId) {
setConnectionState({
status: 'connected',
message: `Connection ${metadata.connectionId} established`,
})
onConnectionEstablished?.(metadata.connectionId)
}
break
case ConnectorSDKEventType.ExitAbort:
setConnectionState({
status: 'idle',
message: 'Connection process aborted',
})
break
case ConnectorSDKEventType.ExitError:
setConnectionState({
status: 'error',
message: 'Error establishing connection',
})
break
}
}
return (
<div className="space-y-4">
<div className="flex gap-4">
{/* Standard Connection Flow */}
<QuilttButton
connectorId={process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID!}
onEvent={handleEvent}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
disabled={connectionState.status === 'connecting'}
>
Connect Account
</QuilttButton>
{/* Pre-selected Institution Flow */}
<QuilttButton
connectorId={process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID!}
institution="Chase"
onEvent={handleEvent}
className="rounded bg-green-600 px-4 py-2 text-white hover:bg-green-700"
disabled={connectionState.status === 'connecting'}
>
Connect Chase Account
</QuilttButton>
</div>
{/* Connection State Display */}
{connectionState.message && (
<div
className={`rounded-lg p-4 ${
connectionState.status === 'error'
? 'bg-red-100 text-red-800'
: connectionState.status === 'connected'
? 'bg-green-100 text-green-800'
: 'bg-blue-100 text-blue-800'
}`}
>
{connectionState.message}
</div>
)}
</div>
)
}
The key events you'll want to handle are:
Open
: Triggered when the connector modal opensLoad
: Fired when an aggregator is selected and loadedExitSuccess
: Indicates a successful connection, providing the connectionId
ExitAbort
: Triggered if the user cancels the processExitError
: Fired if there's an error during connectionThe @quiltt/react
package provides hooks for handling data fetching and caching through GraphQL. The primary hooks you'll use are:
useQuery
: For fetching datauseSubscription
: For real-time updatesuseQuilttClient
: For direct cache access and modificationsThese hooks provide several powerful features:
While this tutorial uses React hooks for client-side data fetching, you can also implement your own server-side GraphQL client using fetch
. This might be preferred if you need to keep sensitive data server-side or want to implement your own caching strategy. Check out our API Reference for details on implementing a custom client.
Now we can create our accounts list component that handles both initial data loading and real-time updates:
// src/components/AccountsList.tsx
'use client'
import { gql, useQuery, useSubscription, useQuilttClient } from '@quiltt/react'
const GET_ACCOUNTS = gql`
query GetAccounts($connectionId: ID!) {
connection(id: $connectionId) {
accounts {
id
name
balance
type
}
}
}
`
const ACCOUNT_UPDATES = gql`
subscription OnAccountUpdate($connectionId: ID!) {
accountUpdate(connectionId: $connectionId) {
id
balance
updatedAt
}
}
`
interface AccountsListProps {
connectionId: string
}
export default function AccountsList({ connectionId }: AccountsListProps) {
const quilttClient = useQuilttClient()
const { data, loading, error } = useQuery(GET_ACCOUNTS, {
variables: { connectionId },
})
// Subscribe to real-time balance updates
useSubscription(ACCOUNT_UPDATES, {
variables: { connectionId },
onData: ({ data }) => {
// Update cache with new balance
quilttClient.cache.modify({
id: quilttClient.cache.identify(data.accountUpdate),
fields: {
balance: () => data.accountUpdate.balance,
},
})
},
})
if (loading) {
return <div>Loading accounts...</div>
}
if (error) {
return <div className="text-red-600">Error loading accounts: {error.message}</div>
}
return (
<div className="space-y-4">
{data?.connection?.accounts.map((account) => (
<div key={account.id} className="rounded-lg bg-white p-4 shadow">
<h3 className="font-semibold">{account.name}</h3>
<p className="text-gray-600">${account.balance.toFixed(2)}</p>
<p className="text-gray-500 text-sm">{account.type}</p>
<p className="text-gray-400 text-xs">Updates automatically</p>
</div>
))}
</div>
)
}
Quiltt provides real-time updates through GraphQL subscriptions. Here's how to implement comprehensive real-time functionality:
// src/components/ConnectionMonitor.tsx
'use client'
import { gql, useSubscription, useQuilttClient } from '@quiltt/react'
const CONNECTION_STATUS = gql`
subscription OnConnectionStatus($connectionId: ID!) {
connectionStatus(connectionId: $connectionId) {
id
status
updatedAt
}
}
`
const SYNC_PROGRESS = gql`
subscription OnSyncProgress($connectionId: ID!) {
syncProgress(connectionId: $connectionId) {
id
progress
stage
estimatedTimeRemaining
}
}
`
interface ConnectionMonitorProps {
connectionId: string
onStatusChange?: (status: string) => void
}
export default function ConnectionMonitor({ connectionId, onStatusChange }: ConnectionMonitorProps) {
const quilttClient = useQuilttClient()
// Monitor connection status changes
useSubscription(CONNECTION_STATUS, {
variables: { connectionId },
onData: ({ data }) => {
// Update connection status in cache
quilttClient.cache.modify({
id: quilttClient.cache.identify({ __typename: 'Connection', id: connectionId }),
fields: {
status: () => data.connectionStatus.status,
},
})
onStatusChange?.(data.connectionStatus.status)
},
})
// Monitor sync progress
useSubscription(SYNC_PROGRESS, {
variables: { connectionId },
onData: ({ data }) => {
console.log('Sync progress:', data.syncProgress)
},
})
return null // This is a monitoring component with no UI
}
This implementation provides several benefits for a multi-aggregator strategy:
While GraphQL subscriptions provide the most seamless real-time experience, you can also implement real-time updates using webhooks. This approach might be preferred for server-side architectures or when you need to integrate with existing event-processing systems. See our Webhooks Documentation for details on setting up webhook-based updates.
When working with multi-aggregator connections, follow these best practices:
By following this structured approach to data access, you'll be able to maintain reliable connections across multiple aggregators while providing a smooth user experience.
The home page (
./src/app/page.tsx
)
serves as the entry point for users to initiate new connections:
// src/app/page.tsx
import ConnectAccounts from '@/components/ConnectAccounts'
import PrefilledConnector from '@/components/PrefilledConnector'
export default function Home() {
return (
<main className="mx-auto max-w-4xl space-y-8 p-6">
<section>
<h1 className="mb-4 font-bold text-2xl">Connect Your Financial Accounts</h1>
<div className="space-y-4">
<div className="rounded-lg bg-white p-6 shadow">
<h2 className="mb-3 font-semibold text-lg">Connect Any Account</h2>
<ConnectAccounts />
</div>
<div className="rounded-lg bg-white p-6 shadow">
<h2 className="mb-3 font-semibold text-lg">Quick Connect: Chase</h2>
<PrefilledConnector />
</div>
</div>
</section>
</main>
)
}
The connections page (
./src/app/connections/page.tsx
)
provides a dashboard view of all connected accounts and their current status:
// src/app/connections/page.tsx
'use client'
import { gql, useQuery } from '@quiltt/react'
import AccountsList from '@/components/AccountsList'
import ConnectionStatusIndicator from '@/components/ConnectionStatusIndicator'
import type { Connection } from '@/types/generated/graphql'
const GET_CONNECTIONS = gql`
query GetConnections {
connections {
id
status
institution {
name
}
accounts {
id
name
balance
type
}
}
}
`
export default function ConnectionsPage() {
const { data, loading, error } = useQuery<{ connections: Array<Connection> }>(GET_CONNECTIONS)
if (loading)
return (
<main className="mx-auto max-w-4xl p-6">
<h1 className="mb-6 font-bold text-2xl">Your Connected Accounts</h1>
<div>Loading connections...</div>
</main>
)
if (error)
return (
<main className="mx-auto max-w-4xl p-6">
<h1 className="mb-6 font-bold text-2xl">Your Connected Accounts</h1>
<div className="text-red-600">Error loading connections: {error.message}</div>
</main>
)
console.log(data)
return (
<main className="mx-auto max-w-4xl p-6">
<h1 className="mb-6 font-bold text-2xl">Your Connected Accounts</h1>
<div className="space-y-6">
{data?.connections.map((connection) => (
<div key={connection.id} className="rounded-lg bg-white p-6 shadow">
<div className="mb-4 flex items-start justify-between">
<h2 className="font-semibold text-lg">{connection.institution.name}</h2>
<ConnectionStatusIndicator status={connection.status} connectionId={connection.id} />
</div>
<AccountsList connectionId={connection.id} />
</div>
))}
</div>
</main>
)
}
The connection details page (
./src/app/connections/[id]/page.tsx
)
shows detailed information for a specific connection, including all linked accounts and repair options if needed:
// src/app/connections/[id]/page.tsx
'use client'
import { Suspense } from 'react'
import { notFound } from 'next/navigation'
import { gql, useQuery } from '@quiltt/react'
import AccountsList from '@/components/AccountsList'
import ConnectionStatusIndicator from '@/components/ConnectionStatusIndicator'
const GET_CONNECTION = gql`
query GetConnection($id: ID!) {
connection(id: $id) {
id
status
institution {
name
}
}
}
`
interface ConnectionPageProps {
params: {
id: string
}
}
export default function ConnectionPage({ params }: ConnectionPageProps) {
const { data, loading, error } = useQuery(GET_CONNECTION, {
variables: { id: params.id },
})
if (loading)
return (
<main className="mx-auto max-w-4xl p-6">
<div>Loading connection details...</div>
</main>
)
if (error)
return (
<main className="mx-auto max-w-4xl p-6">
<div className="text-red-600">Error loading connection: {error.message}</div>
</main>
)
if (!data?.connection) return notFound()
const { connection } = data
return (
<main className="mx-auto max-w-4xl p-6">
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="font-bold text-2xl">{connection.institution.name} Details</h1>
<ConnectionStatusIndicator status={connection.status} connectionId={connection.id} />
</div>
<Suspense fallback={<div>Loading accounts...</div>}>
<AccountsList connectionId={connection.id} />
</Suspense>
</div>
</main>
)
}
Financial data connections can become disrupted for various reasons:
Maintaining reliable connections requires robust error handling and repair capabilities. Let's implement comprehensive error handling:
First, we'll define our connection status handling using the types generated from our GraphQL schema:
TheConnectionStatus
type is generated using@graphql-codegen/typescript
. For detailed instructions on setting up GraphQL Code Generator with Quiltt, see our GraphQL Tooling Tutorial.
// src/lib/connection-status.ts
import type { ConnectionStatus } from '@/types/generated/graphql'
interface StatusHandler {
message: string
severity: 'success' | 'info' | 'warning' | 'error'
action: 'repair' | 'retry' | 'reconnect' | 'check_logs' | 'check_status' | 'contact_support' | null
}
export const handleConnectionStatus = (status: ConnectionStatus): StatusHandler => {
switch (status) {
case 'SYNCED':
return {
message: 'Connected and up to date',
severity: 'success',
action: null,
}
case 'SYNCING':
case 'INITIALIZING':
case 'UPGRADING':
return {
message: 'Updating connection...',
severity: 'info',
action: null,
}
case 'ERROR_REPAIRABLE':
return {
message: 'Connection needs repair',
severity: 'warning',
action: 'repair',
}
case 'ERROR_INSTITUTION':
return {
message: 'Institution temporarily unavailable',
severity: 'error',
action: 'retry',
}
case 'ERROR_PROVIDER':
return {
message: 'Provider error - check remote data',
severity: 'error',
action: 'check_logs',
}
case 'ERROR_SERVICE':
return {
message: 'Service disruption - check status page',
severity: 'error',
action: 'check_status',
}
case 'DISCONNECTED':
return {
message: 'Connection disconnected',
severity: 'error',
action: 'reconnect',
}
default:
return {
message: 'Unknown status',
severity: 'error',
action: 'contact_support',
}
}
}
Let's create a reusable component to display connection status:
// src/components/ConnectionStatusIndicator.tsx
'use client'
import { QuilttButton } from '@quiltt/react'
import { handleConnectionStatus } from '@/lib/connection-status'
import type { ConnectionStatus } from '@/types/generated/graphql'
interface ConnectionStatusIndicatorProps {
status: ConnectionStatus
connectionId: string
}
export default function ConnectionStatusIndicator({ status, connectionId }: ConnectionStatusIndicatorProps) {
const { message, severity, action } = handleConnectionStatus(status)
const getSeverityStyle = () => {
switch (severity) {
case 'success':
return 'bg-green-100 text-green-800'
case 'info':
return 'bg-blue-100 text-blue-800'
case 'warning':
return 'bg-yellow-100 text-yellow-800'
case 'error':
return 'bg-red-100 text-red-800'
}
}
const renderAction = () => {
switch (action) {
case 'repair':
return (
<QuilttButton
connectorId={process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID!}
connectionId={connectionId}
onExitSuccess={(metadata) => {
console.log('Connection repaired:', metadata.connectionId)
}}
className="rounded bg-yellow-600 px-4 py-2 text-white hover:bg-yellow-700"
>
Repair Connection
</QuilttButton>
)
case 'reconnect':
return (
<QuilttButton
connectorId={process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID!}
connectionId={connectionId}
onExitSuccess={(metadata) => {
console.log('Connection reconnected:', metadata.connectionId)
}}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Reconnect
</QuilttButton>
)
case 'check_status':
return (
<a
href="https://status.quiltt.io"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
Check Status Page
</a>
)
// Add other action handlers as needed
default:
return null
}
}
return (
<div className="flex items-center justify-between rounded-lg p-4">
<div className={`rounded-full px-3 py-1 text-sm ${getSeverityStyle()}`}>{message}</div>
{renderAction()}
</div>
)
}
Implementing a multi-aggregator strategy with Quiltt is like building a resilient financial data infrastructure. By following this guide, you've created:
The implementation provides:
Need help with implementation or have questions? The Quiltt team is here to support your journey.