I'm building an Apollo GraphQL server in Typescript and having trouble understanding the correct way to handle things within the type system. Although GraphQL and Apollo are a part of the code, I'm wondering specifically about the TypeScript part. I'm also having trouble understanding the role of interfaces vs types and what the best practice is for each (i.e., when do you use a type vs an interface, how do you handle extending each, etc).
Most of the unclarity I have is in the resolver. I'll leave comments and questions interspersed in the code next to the relevant parts that I'm asking about. Thank you again for any help you can offer:
type BankingAccount = {
id: string;
type: string;
attributes: SpendingAccountAttributes | SavingsAccountAttributes
}
// I've realized this "SpendingAccountAttributes | SavingsAccountAttributes" is the wrong way
// to do what I'm trying to do. I essentially want to the tell the
// type system that this can be one or the other. As I understand it, the way it is written
// will create a Union, returning only the fields that are shared between both types, in this
// case what is essentially in the `BankingAttributes` type. Is that correct?
interface BankingAttributes = {
routingNumber: string;
accountNumber: string;
balance: number;
fundsAvailable: number;
}
// Is it better to remove the `SpendingAccountAttributes` and `SavingsAccountAttribute` specific
// types and just leave them as optional types on the `BankingAttributes`. I will
// in time be creating a resolver for the `SpendingAccount` and `SavingAccount` as standalone
// queries so it seems useful to have them. Not sure 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. Which one should it be? And if "type", how can I
// extend the "BankingAttributes" to "SpendingAccountAttributes" to tell the type system that
// those should be a part of the SpendingAccount's attributes?
export default {
Query: {
bankingAccounts: async(_source: string, _args: [], { dataSources}: Record<string, any>) : Promise<[BankingAccount]> => {
// The following makes a restful API to an `accounts` api route where we pass in the type as an `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,
// accountFeatures fails the compilation with the following error:
// "accountFeatures does not exist on type 'SpendingAccountAttributes | SavingsAccountAttributes'
// What's the best way to handle this so that I can pull the accountFeatures
// for the spending account (which is the only type of account this attribute will be present for)?
accountFeatures,
...other
}
})
return response
}
}
}