I am currently working on setting up an Apollo GraphQL server in Typescript and struggling with understanding the correct approach in dealing with the type system. While GraphQL and Apollo are integral to the code, my main focus is on TypeScript. I am also finding it challenging to grasp the distinction between interfaces and types, and determining the best practices for each (such as when to use a type versus an interface, how to handle extensions, etc).
Most of my confusion lies within the resolver function. I have included comments and questions within the code where I am seeking clarification. I appreciate any guidance you can provide:
type BankingAccount = {
id: string;
type: string;
attributes: SpendingAccountAttributes | SavingsAccountAttributes
}
// A question arises here about the correct way to define 'SpendingAccountAttributes | SavingsAccountAttributes' in order to convey that it can be one or the other. Would this union type only return shared fields between the two types, essentially those in 'BankingAttributes'?
interface BankingAttributes = {
routingNumber: string;
accountNumber: string;
balance: number;
fundsAvailable: number;
}
// Should I eliminate 'SpendingAccountAttributes' and 'SavingsAccountAttributes' specific types and instead make them optional types within 'BankingAttributes'? I will eventually create resolvers for 'SpendingAccount' and 'SavingAccount' as separate queries, so having them may be useful. I am unsure though.
interface SpendingAccountAttributes extends BankingAttributes {
defaultPaymentCardId: string;
defaultPaymentCardLastFour: string;
accountFeatures: Record<string, unknown>;
}
interface SavingsAccountAttributes extends BankingAttributes {
interestRate: number;
interestRateYTD: number;
}
// Mixing types and interfaces seems messy. Is it better to stick with one or the other? If 'type', how can I extend 'BankingAttributes' to 'SpendingAccountAttributes' to indicate that they should be a part of the SpendingAccount's attributes?
export default {
Query: {
bankingAccounts: async(_source: string, _args: [], { dataSources}: Record<string, any>) : Promise<[BankingAccount]> => {
// Here we are making a restful API call to an 'accounts' endpoint, passing in the type as 'includes', i.e. 'api/v2/accounts?types[]=spending&types[]=savings'
const accounts = await dataSources.api.getAccounts(['spending', 'savings'])
const response = accounts.data.map((acc: BankingAccount) => {
const { fundsAvailable, accountFeatures, ...other } = acc.attributes
return {
id: acc.id,
type: acc.type,
balanceAvailableForWithdrawal: fundsAvailable,
// The compilation fails when trying to access 'accountFeatures' with the error: 'accountFeatures does not exist on type 'SpendingAccountAttributes | SavingsAccountAttributes''
// What is the best way to address this and retrieve 'accountFeatures' for the spending account (where this attribute is present)?
accountFeatures,
...other
}
})
return response
}
}
}