npm package: @lantern-ai/hubspot-core-utility
Description: Shared HubSpot helpers for React and Node usage
Latest version: 1.1.6

# @lantern/hubspot-core-utility

A comprehensive utility package for integrating HubSpot services into both client-side (React) and server-side (Node.js) applications.

## Features and Functionalities

This utility provides a robust set of tools to interact with the HubSpot API, covering everything from configuration and client management to specific CRM operations.

### Core Concepts

The package is designed with flexibility in mind, offering two primary ways to interact with HubSpot:

1.  **Modern Client (`createHubspotClient`)**: A factory function that returns a fully typed HubSpot API client. This is the recommended approach for new integrations.
2.  **Legacy Helper Functions**: A suite of standalone, exported functions for common operations (e.g., `getHubSpotContact`, `createHubSpotDeal`). These are great for quick, one-off tasks or for maintaining existing codebases.

### Configuration Management

Secure and straightforward configuration is handled via a set of in-memory helpers, ensuring your API keys are managed safely on the server.

-   **`initializeHubspotFromKey(key)`**: The primary method to set up the utility with your HubSpot API key.
-   **`setHubspotConfig(config)`**, **`getHubspotConfig()`**, **`clearHubspotConfig()`**: Granular functions to manage the HubSpot configuration object manually.

### React Hook

For client-side applications, the package provides a custom hook to interact with HubSpot data.

-   **`useHubspotContacts()`**: A React hook that simplifies fetching and managing HubSpot contacts within your components.

### Server-Side API Functions

A rich set of functions to manage various HubSpot objects directly from your backend.

#### Contact Management
-   `getHubSpotContact(id)`
-   `createHubSpotContact(properties)`
-   `updateHubSpotContact(id, properties)`
-   `searchContactsByEmail(email)`

#### Deal Management
-   `getHubSpotDeal(id)`
-   `createHubSpotDeal(properties)`
-   `updateHubSpotDeal(id, properties)`
-   `getAllHubSpotDeals()`
-   `searchHubSpotDeals(query)`
-   `getHubSpotDealDetails(id)`

#### Product Management
-   `getHubSpotProduct(id)`
-   `getHubSpotProducts()`
-   `getHubSpotProductBySku(sku)`

#### Other Utilities
-   **Associations**: `createHubSpotAssociation(...)` to link different CRM objects.
-   **Timeline Events**: `createTimelineEvent(...)` to add custom events to an object's timeline.
-   **Conversion Tracking**: `trackHubSpotConversion(...)` for marketing analytics.
-   **Low-Level Access**: `hubspotAxios` is exported, giving you direct access to the pre-configured Axios instance for custom or unsupported API calls.

## Quick start

Install (from monorepo use local build / link; when published use npm/yarn):

```bash
# from package folder
npm install
npm run build
# in consumer project (example local link)
npm link @lantern/hubspot-core-utility
```

Initialize once on server startup:

```ts
// server/index.ts (or next.js getServerSideProps / server bootstrap)
import { initializeHubspotFromKey } from '@lantern/hubspot-core-utility';

initializeHubspotFromKey(process.env.HUBSPOT_API_KEY);
```

If your package does not export `initializeHubspotFromKey`, use:
```ts
import { setHubspotConfig } from '@lantern/hubspot-core-utility';
setHubspotConfig({ apiKey: process.env.HUBSPOT_API_KEY });
```

After initialization, anywhere in server or client code (hook will prefer stored config / env fallback):

```ts
import { createHubspotClient } from '@lantern/hubspot-core-utility';

const client = createHubspotClient(); // uses stored config or process.env.HUBSPOT_API_KEY
await client.upsertContactByEmail({ email: 'a@b.com', firstname: 'A' });
```

---

## API

- initializeHubspotFromKey(hapikey?: string) — convenience initializer that calls setHubspotConfig under the hood.
- setHubspotConfig(cfg: Partial<HubspotConfig>) — set in-memory config (server startup).
- getHubspotConfig() — read in-memory config.
- createHubspotClient(config?: HubspotConfig) — returns a client with methods:
  - getContactByEmail(email)
  - createContact(contact)
  - upsertContactByEmail(contact)
  - axiosInstance (raw axios)
- useHubspotContacts(config?: HubspotConfig) — React hook returning:
  - loading, contacts, error, client, fetchByEmail(email), upsertContact(contact)

---

## Example: Server initialization (recommended)

```ts
// server/bootstrap.ts
import { initializeHubspotFromKey } from '@lantern/hubspot-core-utility';

export function bootstrap() {
  // Only run on server
  if (typeof window === 'undefined') {
    initializeHubspotFromKey(process.env.HUBSPOT_API_KEY);
  }
}
```

---

## Example: React component using the hook

```tsx
// filepath: examples/components/ContactManager.tsx
import React, { useEffect } from 'react';
import { useHubspotContacts } from '@lantern/hubspot-core-utility';

export function ContactManager({ email }: { email?: string }) {
  const { loading, contacts, fetchByEmail, upsertContact } = useHubspotContacts();

  useEffect(() => {
    if (email) {
      fetchByEmail(email).catch(console.error);
    }
  }, [email, fetchByEmail]);

  async function handleUpsert() {
    try {
      await upsertContact({ email: 'new@cust.com', firstname: 'New', lastname: 'Cust' });
      // maybe re-fetch or show success
    } catch (e) {
      console.error(e);
    }
  }

  return (
    <div>
      <button onClick={handleUpsert} disabled={loading}>
        Upsert demo contact
      </button>
      {loading && <div>Loading…</div>}
      {contacts?.length ? (
        <div>
          <h4>Contact</h4>
          <pre>{JSON.stringify(contacts[0], null, 2)}</pre>
        </div>
      ) : null}
    </div>
  );
}
```

Notes:
- The hook does NOT auto-fetch on mount (call fetchByEmail explicitly). This keeps behavior predictable and testable.
- The hook uses the in-memory config (set via initializeHubspotFromKey / setHubspotConfig) or falls back to process.env.HUBSPOT_API_KEY.

---

## Example: Server endpoint (Express)

```ts
// server/routes/hubspot.ts
import express from 'express';
import { createHubspotClient } from '@lantern/hubspot-core-utility';
const router = express.Router();

router.post('/contacts/upsert', async (req, res) => {
  try {
    const client = createHubspotClient();
    const contact = await client.upsertContactByEmail(req.body);
    res.json(contact);
  } catch (err: any) {
    res.status(err?.status || 500).json({ message: err?.message || 'error' });
  }
});

export default router;
```

---

## Examples: Using the full public API

The package entrypoint exports the primary helpers and the convenience (legacy) wrappers so you can import everything from the package root.

- Recommended: initialize the in-memory config on server startup (hapikey). For tests or browser-like environments use `setHubspotConfig` instead.

```ts
import {
  // core helpers
  createHubspotClient,
  useHubspotContacts,
  // config helpers
  initializeHubspotFromKey,
  setHubspotConfig,
  getHubspotConfig,
  clearHubspotConfig,
  // legacy convenience wrappers
  getHubSpotContact,
  createHubSpotContact,
  updateHubSpotContact,
  getHubSpotProduct,
  getHubSpotProducts,
  getHubSpotProductBySku,
  createHubSpotDeal,
  updateHubSpotDeal,
  getHubSpotDeal,
  getHubSpotDealDetails,
  getHubSpotDealRawProperties,
  getAllHubSpotDeals,
  searchHubSpotDeals,
  searchContactsByEmail,
  createHubSpotAssociation,
  createTimelineEvent,
  trackHubSpotConversion,
  hubspotAxios,
} from '@lantern/hubspot-core-utility';
```

Server initialization (recommended):

```ts
// server/bootstrap.ts
import { initializeHubspotFromKey } from '@lantern/hubspot-core-utility';

if (typeof window === 'undefined') {
  initializeHubspotFromKey(process.env.HUBSPOT_API_KEY);
}
```

Using the client directly:

```ts
const client = createHubspotClient();
await client.upsertContactByEmail({ email: 'a@b.com', firstname: 'A' });
```

Using the legacy convenience wrappers (examples):

```ts
// read a contact
const contact = await getHubSpotContact('contact-id');

// create / update contact
const created = await createHubSpotContact({ email: 'new@cust.com', firstname: 'New' });
await updateHubSpotContact(created.id, { lastname: 'Customer' });

// products
const products = await getHubSpotProducts();
const bySku = await getHubSpotProductBySku('sku-123');

// deals
const deal = await createHubSpotDeal({ dealname: 'Purchase', amount: 100 });
await updateHubSpotDeal(deal.id, { amount: 120 });
const dealDetails = await getHubSpotDealDetails(deal.id);

// raw properties & associations
const raw = await getHubSpotDealRawProperties(deal.id);

// search
const foundContacts = await searchContactsByEmail('someone@example.com');
const searchDeals = await searchHubSpotDeals([], ['dealname'], 50);

// create association
await createHubSpotAssociation('deals', deal.id, 'contacts', contact?.id || '');

// optional timeline event (non-fatal)
await createTimelineEvent(contact?.id || '', { templateId: 'template-1', properties: { note: 'Test event' } });

// track conversion (creates deal, optionally updates contact/associations)
await trackHubSpotConversion({ contactId: contact?.id, amount: 200, productId: 123 });

// if you need raw axios instance
hubspotAxios.request({ method: 'get', url: '/crm/v3/objects/contacts' });
```

Notes
- Most helpers are server-oriented and assume the hapikey or access token is only available server-side. Do not call `initializeHubspotFromKey` from client bundles — it includes a runtime guard and will throw if executed where `window` is defined. Use `setHubspotConfig` in tests or browser-like environments.

---

## Local demo setup (examples/demo + examples/react)

This repository includes a small Express demo server (`examples/demo`) and a Vite + React demo app (`examples/react`). The React app calls the demo server, so your HubSpot credentials remain server-side.

Follow these steps to run both locally:

1) Prepare and start the demo server

```bash
# Install server deps
cd examples/demo
npm install

# Add your HubSpot credential to a local .env file (Private App token is recommended).
# Replace the value below with your own token (starts with "pat-")
# Do NOT commit this file or share the token publicly.
echo "HUBSPOT_ACCESS_TOKEN=pat-YOUR_PRIVATE_APP_TOKEN" > .env

# Start the server (reads .env when started). You can also pass the token inline:
# HUBSPOT_ACCESS_TOKEN=pat-YOUR_PRIVATE_APP_TOKEN npm run dev

# Start in the current shell (so you can see logs):
export $(grep -v '^#' .env | xargs)
npm run dev
```

Server endpoints (default PORT 3001):
- GET /health
- GET /contact?email=someone@example.com
- POST /contacts/upsert  (JSON body { email, firstname, lastname })

2) Prepare and start the React demo app

```bash
# In a separate terminal
cd examples/react
npm install

# Optional: tell the Vite dev server where the demo server is running.
# By default the client uses http://localhost:3001. To override, create a Vite env file:
echo "VITE_DEMO_SERVER=http://localhost:3001" > .env

# Start the React dev server
npm run dev
```

Open the React app at: http://localhost:5173/
The app will call the demo server (http://localhost:3001) which performs HubSpot API requests server-side.

Security notes

- Always keep HubSpot credentials server-side. Use a Private App token (pat-...) when interacting with CRM v3 endpoints. Developer API keys / hapikeys (na2-...) are legacy and often rejected by CRM v3 endpoints.
- Never commit `.env` or secrets into git. Add `examples/demo/.env` and `examples/react/.env` to your global `.gitignore` or local ignore rules.
- If an access token is accidentally exposed, rotate/revoke it in HubSpot immediately.

Troubleshooting

- CORS errors in the browser (preflight) usually mean the client tried to call api.hubapi.com directly. Make sure the React app is calling the demo server endpoints (GET /contact, POST /contacts/upsert) and not HubSpot's API URLs.

- If the demo server returns 401 Unauthorized:
  - Confirm `HUBSPOT_ACCESS_TOKEN` is present in `examples/demo/.env` and the server was restarted after adding it.
  - Verify the token works directly against HubSpot:

```bash
# Replace TOKEN and an email to test
curl -s -X POST 'https://api.hubapi.com/crm/v3/objects/contacts/search' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer TOKEN" \
  -d '{"filterGroups":[{"filters":[{"propertyName":"email","operator":"EQ","value":"test@gmail.com"}]}],"properties":["email","firstname","lastname"]}' | jq .
```

- To see detailed server errors, run the demo server in the foreground (do not run it as a background job) and inspect the console logs while issuing requests.

Convenience (optional)

- If you'd like to start both servers with one command you can use `concurrently` or `npm-run-all`. Example (add to repo-level scripts):

```json
"scripts": {
  "examples:demo:start": "cd examples/demo && export $(grep -v '^#' .env | xargs) && npm run dev",
  "examples:react:start": "cd examples/react && npm run dev",
  "examples:start": "concurrently \"npm run examples:demo:start\" \"npm run examples:react:start\""
}
```

(Install `concurrently` as a dev dependency if you use the above.)

---

## Build & test

From package root:

```bash
npm install
npm run build
npm test
```

---

## Security notes

- Do NOT call initializeHubspotFromKey / setHubspotConfig from client-side code. Only initialize on server. Exposing hapikey in browser will compromise your HubSpot account.
- Prefer server-side usage (API routes, server-rendered pages). For client needs, provide a server proxy endpoint that uses this utility internally.

Note: `initializeHubspotFromKey` includes a runtime guard and will throw if invoked in a browser-like environment (for example when `window` is defined or in jsdom). This helps prevent accidental client-side initialization and leaking of the hapikey. For tests or any environment where `window` exists, call `setHubspotConfig({ apiKey: '...' })` instead.

---