@natify/graphql-apollo
v1.0.2
Published
GraphQL adapter for Natify Framework using `@apollo/client`.
Readme
@natify/graphql-apollo
GraphQL adapter for Natify Framework using @apollo/client.
Installation
pnpm add @natify/graphql-apollo @apollo/client graphqliOS
cd ios && pod install && cd ..Android
No additional configuration required.
Usage
Basic Configuration
import { NatifyProvider } from "@natify/core";
import { ApolloGraphQLAdapter } from "@natify/graphql-apollo";
const graphqlAdapter = new ApolloGraphQLAdapter({
uri: "https://api.example.com/graphql",
});
const config = {
graphql: graphqlAdapter,
// ... other adapters
};
function App() {
return (
<NatifyProvider config={config}>
<MyApp />
</NatifyProvider>
);
}Advanced Configuration
const graphqlAdapter = new ApolloGraphQLAdapter({
uri: "https://api.example.com/graphql",
headers: {
"X-App-Version": "1.0.0",
"X-Platform": "mobile",
},
cacheEnabled: true,
timeout: 30000,
onAuthError: async () => {
// Redirect to login when there's a 401 error
await logout();
navigation.reset([{ name: "auth/Login" }]);
},
onNetworkError: (error) => {
console.error("Network error:", error);
// Show error notification
},
});Authentication with Token
Set Token After Login
import { useAdapter, GraphQLPort, StoragePort } from "@natify/core";
function useAuth() {
const graphql = useAdapter<GraphQLPort>("graphql");
const secureStorage = useAdapter<StoragePort>("secureStorage");
const login = async (email: string, password: string) => {
// Login using HTTP (or GraphQL mutation)
const response = await http.post("/auth/login", { email, password });
// Save token
const token = response.data.token;
await secureStorage.setItem("auth_token", token);
// Inject token into GraphQL
graphql.setAuthToken(token);
};
const logout = async () => {
// Clear token
graphql.clearAuthToken();
await secureStorage.removeItem("auth_token");
// Clear cache
await graphql.clearCache();
};
return { login, logout };
}Restore Token on App Start
import { useEffect } from "react";
import { useAdapter, GraphQLPort, StoragePort } from "@natify/core";
function AppInitializer() {
const graphql = useAdapter<GraphQLPort>("graphql");
const secureStorage = useAdapter<StoragePort>("secureStorage");
useEffect(() => {
const restoreAuth = async () => {
const token = await secureStorage.getItem<string>("auth_token");
if (token) {
graphql.setAuthToken(token);
}
};
restoreAuth();
}, []);
return null;
}Queries
Simple Query
import { useAdapter, GraphQLPort } from "@natify/core";
const GET_USERS = `
query GetUsers {
users {
id
name
email
}
}
`;
function UserList() {
const graphql = useAdapter<GraphQLPort>("graphql");
const [users, setUsers] = useState([]);
useEffect(() => {
const loadUsers = async () => {
const result = await graphql.query<{ users: User[] }>(GET_USERS);
setUsers(result.data.users);
};
loadUsers();
}, []);
return (
<View>
{users.map(user => (
<Text key={user.id}>{user.name}</Text>
))}
</View>
);
}Query with Variables
const GET_USER = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
}
}
}
`;
const loadUser = async (userId: string) => {
const result = await graphql.query<{ user: User }>(GET_USER, {
variables: { id: userId },
});
return result.data.user;
};Query with Cache Options
// Always get fresh data from server
const result = await graphql.query(GET_USERS, {
fetchPolicy: "network-only",
});
// Use cache if available, otherwise make request
const result = await graphql.query(GET_USERS, {
fetchPolicy: "cache-and-network",
});
// Only use cache (don't make request)
const result = await graphql.query(GET_USERS, {
fetchPolicy: "cache-only",
});Mutations
Simple Mutation
const CREATE_USER = `
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`;
const createUser = async (name: string, email: string) => {
const result = await graphql.mutate<{ createUser: User }>(CREATE_USER, {
variables: {
input: { name, email },
},
});
return result.data.createUser;
};Mutation with Error Handling
const UPDATE_PROFILE = `
mutation UpdateProfile($input: UpdateProfileInput!) {
updateProfile(input: $input) {
id
name
email
}
}
`;
const updateProfile = async (input: UpdateProfileInput) => {
try {
const result = await graphql.mutate<{ updateProfile: User }>(UPDATE_PROFILE, {
variables: { input },
errorPolicy: "all", // Return data even if there are errors
});
if (result.errors && result.errors.length > 0) {
// Handle GraphQL errors
result.errors.forEach(error => {
console.error("GraphQL Error:", error.message);
});
}
return result.data.updateProfile;
} catch (error) {
if (error instanceof NatifyError) {
// Handle network or validation errors
console.error("Error:", error.message);
}
throw error;
}
};Subscriptions
Simple Subscription
const MESSAGE_SUBSCRIPTION = `
subscription OnNewMessage($chatId: ID!) {
onNewMessage(chatId: $chatId) {
id
text
sender {
id
name
}
createdAt
}
}
`;
function ChatScreen({ chatId }: { chatId: string }) {
const graphql = useAdapter<GraphQLPort>("graphql");
const [messages, setMessages] = useState<Message[]>([]);
useEffect(() => {
const subscription = graphql.subscribe<{ onNewMessage: Message }>(
MESSAGE_SUBSCRIPTION,
{
variables: { chatId },
}
);
const { unsubscribe } = subscription.subscribe(
(result) => {
// Add new message
setMessages(prev => [...prev, result.data.onNewMessage]);
},
(error) => {
console.error("Subscription error:", error);
}
);
return () => {
unsubscribe();
};
}, [chatId]);
return (
<View>
{messages.map(message => (
<Text key={message.id}>{message.text}</Text>
))}
</View>
);
}UseCase with GraphQL
import { GraphQLPort } from "@natify/core";
const GET_PRODUCTS = `
query GetProducts($category: String) {
products(category: $category) {
id
name
price
image
}
}
`;
export class GetProductsUseCase {
constructor(private readonly graphql: GraphQLPort) {}
async execute(category?: string): Promise<Product[]> {
const result = await this.graphql.query<{ products: Product[] }>(GET_PRODUCTS, {
variables: { category },
});
return result.data.products;
}
}Common Use Cases
Login with GraphQL
const LOGIN_MUTATION = `
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user {
id
name
email
}
}
}
`;
const login = async (email: string, password: string) => {
const result = await graphql.mutate<{ login: { token: string; user: User } }>(
LOGIN_MUTATION,
{
variables: { email, password },
}
);
const { token, user } = result.data.login;
// Save token
await secureStorage.setItem("auth_token", token);
// Inject token into GraphQL
graphql.setAuthToken(token);
return user;
};Automatically Refresh Token
const graphqlAdapter = new ApolloGraphQLAdapter({
uri: "https://api.example.com/graphql",
onAuthError: async () => {
// Try to refresh token
const refreshToken = await secureStorage.getItem<string>("refresh_token");
if (refreshToken) {
const response = await http.post("/auth/refresh", { refreshToken });
const newToken = response.data.token;
// Update token
await secureStorage.setItem("auth_token", newToken);
graphql.setAuthToken(newToken);
// Retry original request
return;
}
// If no refresh token, logout
await logout();
},
});Cursor Pagination
const GET_PRODUCTS_PAGINATED = `
query GetProducts($cursor: String, $limit: Int) {
products(cursor: $cursor, limit: $limit) {
items {
id
name
price
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
const loadMoreProducts = async (cursor?: string) => {
const result = await graphql.query<{
products: { items: Product[]; pageInfo: PageInfo };
}>(GET_PRODUCTS_PAGINATED, {
variables: { cursor, limit: 20 },
});
return result.data.products;
};API
GraphQLPort
| Method | Description |
|--------|-------------|
| query<T>(query, options?) | Executes a GraphQL query |
| mutate<T>(mutation, options?) | Executes a mutation |
| subscribe<T>(subscription, options?) | Subscribes to a subscription |
| setAuthToken(token) | Sets authentication token |
| getAuthToken() | Gets current token |
| clearAuthToken() | Clears token |
| clearCache() | Clears cache |
| resetClient() | Resets client |
GraphQLOptions
interface GraphQLOptions {
variables?: Record<string, any>;
context?: Record<string, any>;
fetchPolicy?: 'cache-first' | 'cache-and-network' | 'network-only' | 'cache-only' | 'no-cache';
errorPolicy?: 'none' | 'ignore' | 'all';
}GraphQLResult
interface GraphQLResult<T> {
data: T;
errors?: Array<{
message: string;
locations?: Array<{ line: number; column: number }>;
path?: Array<string | number>;
extensions?: Record<string, any>;
}>;
extensions?: Record<string, any>;
}Notes
- Authentication Token: Automatically injected into all requests when set with
setAuthToken() - Cache: Apollo Client handles cache automatically. Use
clearCache()after logout - Errors: All errors are converted to
NatifyErrorwith appropriate codes - Subscriptions: Require a GraphQL server that supports WebSockets or Server-Sent Events
- TypeScript: Queries/mutations can be typed using generics
Module Integration
import { createModule } from "@natify/core";
import { GetProductsUseCase } from "./usecases/GetProductsUseCase";
export const ProductsModule = createModule("products", "Products")
.requires("graphql")
.useCase("getProducts", (adapters) => {
return new GetProductsUseCase(adapters.graphql);
})
.build();