startsimpli-api
v0.1.0
Published
Type-safe Django REST API client for StartSimpli apps
Readme
@startsimpli/api
Type-safe Django REST API client for StartSimpli Next.js applications.
Overview
This package provides a type-safe TypeScript client for the Django REST API backend (start-simpli-api). It handles authentication, error normalization, pagination, and provides endpoint wrappers for all Django models.
IMPORTANT: This is a Django REST API client - NO Prisma, NO database code. All data lives in the Django backend.
Installation
npm install @startsimpli/apiUsage
Basic Setup
import { createStartSimpliApi } from '@startsimpli/api';
const api = createStartSimpliApi({
baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1',
getToken: async () => {
// Get token from your auth provider
const session = await getServerSession();
return session?.accessToken || null;
},
});
// Use API endpoints
const contacts = await api.contacts.list({ tier: 1 });
const orgs = await api.organizations.getByStage('seed');Contacts API
// List contacts with filters
const contacts = await api.contacts.list(
{ tier: 1, has_linkedin: true }, // filters
{ page: 1, page_size: 20 }, // pagination
{ ordering: '-created_at' } // sorting
);
// Get single contact
const contact = await api.contacts.get('contact-uuid');
// Create contact with assertions
const newContact = await api.contacts.create({
name: 'John Doe',
email: '[email protected]',
title: 'Partner',
write_tags: ['tier_1', 'focus:fintech'],
write_metrics: { enrichment_score: 0.95 },
write_profiles: { linkedin: 'https://linkedin.com/in/johndoe' },
});
// Update contact
const updated = await api.contacts.update('contact-uuid', {
title: 'Managing Partner',
write_tags: ['tier_1', 'vip'],
});
// Search contacts
const results = await api.contacts.search('john');
// Get contacts by firm
const firmContacts = await api.contacts.getByFirm('firm-uuid');Organizations API
// List organizations with filters
const orgs = await api.organizations.list(
{
tier: 1,
stage: 'seed',
check_size_min_gte: 100000,
check_size_max_lte: 1000000,
},
{ page: 1, page_size: 20 },
{ ordering: '-aum' }
);
// Get single organization
const org = await api.organizations.get('org-uuid');
// Create organization with assertions
const newOrg = await api.organizations.create({
name: 'Acme Ventures',
domain: 'acmevc.com',
location: 'San Francisco, CA',
write_tags: ['tier_1', 'stage:seed', 'focus:fintech'],
write_metrics: {
check_size_min: 500000,
check_size_max: 2000000,
aum: 100000000,
},
write_profiles: {
linkedin: 'https://linkedin.com/company/acme-ventures',
website: 'https://acmevc.com',
},
});
// Get by check size range
const firms = await api.organizations.getByCheckSizeRange(100000, 1000000);Entities API (Low-level)
// Tags
const tags = await api.entities.listTags();
// Entity Tags
const entityTags = await api.entities.listEntityTags(
{ page: 1, page_size: 50 },
{ entity_id: 'some-uuid', category: 'focus' }
);
// Metrics
const metrics = await api.entities.listMetrics(
undefined,
{ entity_id: 'some-uuid', type: 'financial' }
);
// Profiles
const profiles = await api.entities.listProfiles(
undefined,
{ entity_id: 'some-uuid', type: 'professional' }
);Using Direct Client
For custom endpoints not covered by wrappers:
import { createApiClient } from '@startsimpli/api';
const client = createApiClient({
baseUrl: 'http://localhost:8000/api/v1',
getToken: async () => getAccessToken(),
});
// Direct fetch calls
const data = await client.fetch.get('/custom-endpoint/', {
params: { key: 'value' }
});
const created = await client.fetch.post('/custom-endpoint/', {
name: 'test'
});Next.js API Routes Middleware
With Auth
import { withAuth } from '@startsimpli/api/middleware';
export const GET = withAuth(async (request, context) => {
const { userId, token, isAuthenticated } = context;
if (!isAuthenticated) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Use token to call Django API
return NextResponse.json({ userId, token });
});With Validation
import { withValidation } from '@startsimpli/api/middleware';
import { z } from 'zod';
const createContactSchema = z.object({
name: z.string().min(1),
email: z.string().email().optional(),
tier: z.number().int().min(1).max(3).optional(),
});
export const POST = withValidation(
async (request, { body }) => {
// body is typed and validated
const contact = await createContact(body);
return NextResponse.json(contact);
},
{ body: createContactSchema }
);With Error Handling
import { withErrorHandling } from '@startsimpli/api/middleware';
export const GET = withErrorHandling(async (request) => {
// Errors are automatically caught and formatted
const data = await somethingThatMightThrow();
return NextResponse.json(data);
});Composing Middleware
import { withAuth, withErrorHandling, withValidation } from '@startsimpli/api/middleware';
import { z } from 'zod';
const schema = z.object({ name: z.string() });
export const POST = withErrorHandling(
withAuth(
withValidation(
async (request, { body }, authContext) => {
// Fully typed, validated, and authenticated
return NextResponse.json({ success: true });
},
{ body: schema }
)
)
);Error Handling
import {
isApiException,
isValidationError,
isAuthError,
isNotFoundError,
} from '@startsimpli/api';
try {
const contact = await api.contacts.get('invalid-id');
} catch (error) {
if (isValidationError(error)) {
console.log('Validation errors:', error.errors);
} else if (isAuthError(error)) {
console.log('Auth error:', error.status);
} else if (isNotFoundError(error)) {
console.log('Contact not found');
} else if (isApiException(error)) {
console.log('API error:', error.message);
}
}TypeScript Types
All types are exported for use in your application:
import type {
Contact,
Organization,
Entity,
Tag,
Metric,
Profile,
Attribute,
PaginatedResponse,
ApiError,
CreateContactRequest,
UpdateContactRequest,
ContactFilters,
} from '@startsimpli/api';Architecture
Django Integration
This package is designed to work with the Django REST API:
- Base URL:
http://localhost:8000/api/v1/(configurable) - Auth: Bearer token from
@startsimpli/auth - Pagination: Django REST Framework format (
page,page_size) - Filtering: Django-filter query params (
field__gte=100,field__in=1,2,3)
Entity System
Django uses a generic Entity model with assertions (Tags, Metrics, Profiles, Attributes):
// Entity with assertions
{
id: 'uuid',
entity_type: 'contact',
// Canonical fields
name: 'John Doe',
email: '[email protected]',
// Computed from assertions
tier: 1, // from tags
linkedin: 'https://...', // from profiles
enrichment_score: 0.95, // from metrics
// Full assertions
tags: [{ category: 'quality', name: 'tier_1', ... }],
metrics: [{ type: 'quality', subtype: 'enrichment_score', value: 0.95 }],
profiles: [{ type: 'professional', subtype: 'linkedin', identifier: '...' }],
}Development
# Run tests
npm test
# Type check
npm run type-checkLicense
MIT
