---
title: "Add Shared Utils"
description: "Create packages/utils with formatDate, slugify, and truncate functions, then import and use them in the snippet manager."
canonical_url: "https://vercel.com/academy/production-monorepos/add-shared-utils"
md_url: "https://vercel.com/academy/production-monorepos/add-shared-utils.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-11T20:16:03.066Z"
content_type: "lesson"
course: "production-monorepos"
course_title: "Production Monorepos with Turborepo"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Add Shared Utils

# Add shared utils

We're formatting dates with `toLocaleDateString()` in apps/snippet-manager. Utility functions like date formatting, text slugification, and string truncation are perfect candidates for shared packages - they're pure functions with no UI dependencies.

You'll create packages/utils with common utilities that work across all apps. This demonstrates that monorepos aren't just for sharing components - you can share any code.

## Outcome

Create packages/utils with **utility** functions and use formatDate in the snippet manager.

## Fast track

1. Create packages/utils package structure
2. Add utility functions (formatDate, slugify, truncate, validateEmail)
3. Export functions from package
4. Use formatDate in snippet manager app

## Hands-on exercise 4.2

Create shared utilities package with common helper functions.

**Requirements:**

1. Create `packages/utils` **directory**\*\*\*\*
2. Add package.json with TypeScript config
3. Create `src/index.ts` with utility functions
4. Export formatDate, slugify, truncate, validateEmail
5. Add utils dependency to apps/snippet-manager
6. Replace date formatting with formatDate utility
7. Test in both dev and build

**Implementation hints:**

- Use Intl.DateTimeFormat for formatDate
- slugify converts text to URL-safe format
- truncate limits string length with ellipsis
- validateEmail uses regex pattern
- Export all functions from src/index.ts

## Create utils package

Create the directory structure:

```bash
mkdir -p packages/utils/src
```

Create `packages/utils/package.json`. Not in the `src` directory but in `utils`:

```json title="packages/utils/package.json"
{
  "name": "@geniusgarage/utils",
  "version": "1.0.0",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "lint": "eslint ."
  },
  "devDependencies": {
    "@geniusgarage/typescript-config": "workspace:*",
    "@geniusgarage/eslint-config": "workspace:*",
    "eslint": "^9",
    "typescript": "^5"
  }
}
```

Create `packages/utils/tsconfig.json`:

```json title="packages/utils/tsconfig.json"
{
  "extends": "@geniusgarage/typescript-config/base.json",
  "compilerOptions": {
    "outDir": "./dist"
  },
  "include": ["src"]
}
```

Notice it extends the shared TypeScript config from `packages/typescript-config`!

## Create utility functions

Create `packages/utils/src/index.ts`:

```ts title="packages/utils/src/index.ts"
// TODO: Export formatDate function that:
//   - Takes a Date parameter
//   - Returns formatted string "Jan 15, 2024"
//   - Uses Intl.DateTimeFormat with en-US, month: 'short', day: 'numeric', year: 'numeric'

// TODO: Export slugify function that:
//   - Takes a string parameter
//   - Returns URL-safe slug (lowercase, replace spaces with hyphens, remove special chars)
//   - Example: "Hello World!" → "hello-world"

// TODO: Export truncate function that:
//   - Takes text string and maxLength number
//   - Returns truncated string with "..." if longer than maxLength
//   - Example: truncate("Hello World", 5) → "Hello..."

// TODO: Export validateEmail function that:
//   - Takes email string
//   - Returns boolean (true if valid email format)
//   - Uses regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
```

**Your task:** Implement all four utility functions.

**Hints:**

- formatDate: `new Intl.DateTimeFormat('en-US', { ... }).format(date)`
- slugify: Chain `.toLowerCase()`, `.replace()` calls
- truncate: Check `text.length <= maxLength`, use `text.slice(0, maxLength) + '...'`
- validateEmail: `return regex.test(email)`

Solution: if you didn't come here to write functions

```ts title="packages/utils/src/index.ts"
export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('en-US', {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  }).format(date)
}

export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .trim()
}

export function truncate(text: string, maxLength: number): string {
  if (text.length <= maxLength) return text
  return text.slice(0, maxLength) + '...'
}

export function validateEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  return regex.test(email)
}
```

## Add utils to snippet manager

Install the utils package:

```bash
pnpm add @geniusgarage/utils --filter @geniusgarage/snippet-manager --workspace
pnpm install
```

### Update snippet interface

Open `apps/snippet-manager/app/page.tsx` and change `createdAt` from string to Date:

```tsx title="apps/snippet-manager/app/page.tsx" {7}
interface Snippet {
  id: number
  title: string
  language: string
  code: string
  tags: string[]
  createdAt: Date  // Changed from string to Date
}
```

### Import formatdate

Add the import at the top:

```tsx title="apps/snippet-manager/app/page.tsx" {4}
import { useState } from 'react'
import { Button } from '@geniusgarage/ui/button'
import { SnippetCard } from '@geniusgarage/ui/snippet-card'
import { formatDate } from '@geniusgarage/utils'
```

### Update mock data

Change the mock data to use Date objects:

```tsx title="apps/snippet-manager/app/page.tsx"
const initialSnippets: Snippet[] = [
  {
    id: 1,
    title: 'Array Reduce Pattern',
    language: 'javascript',
    code: 'const sum = arr.reduce((acc, n) => acc + n, 0)',
    tags: ['javascript', 'array', 'functional'],
    createdAt: new Date('2024-01-15'),  // Date object
  },
  {
    id: 2,
    title: 'React useEffect Cleanup',
    language: 'typescript',
    code: `useEffect(() => {
  const timer = setTimeout(() => {}, 1000)
  return () => clearTimeout(timer)
}, [])`,
    tags: ['react', 'hooks', 'typescript'],
    createdAt: new Date('2024-02-20'),  // Date object
  },
  {
    id: 3,
    title: 'Promise.all Pattern',
    language: 'javascript',
    code: 'const results = await Promise.all(promises.map(p => p()))',
    tags: ['javascript', 'async', 'promises'],
    createdAt: new Date('2024-03-10'),  // Date object
  },
]
```

### Update handlecreatesnippet

Replace the date formatting with formatDate utility:

```tsx title="apps/snippet-manager/app/page.tsx" {10}
const handleCreateSnippet = () => {
  if (!newSnippet.title || !newSnippet.code) return

  const snippet: Snippet = {
    id: Date.now(),
    title: newSnippet.title,
    language: newSnippet.language,
    code: newSnippet.code,
    tags: newSnippet.tags.split(',').map(t => t.trim()).filter(Boolean),
    createdAt: new Date()  // Now a Date object
  }

  setSnippets([snippet, ...snippets])
  setShowModal(false)
  setNewSnippet({ title: '', language: 'javascript', code: '', tags: '' })
}
```

### Update snippetcard rendering

Pass formatted date to SnippetCard:

```tsx title="apps/snippet-manager/app/page.tsx" {8}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
  {snippets.map((snippet) => (
    <SnippetCard
      key={snippet.id}
      title={snippet.title}
      language={snippet.language}
      code={snippet.code}
      tags={snippet.tags}
      createdAt={formatDate(snippet.createdAt)}
    />
  ))}
</div>
```

Now dates are formatted consistently using the shared utility!

## Try it

### 1. Test in development

```bash
pnpm --filter @geniusgarage/snippet-manager dev
```

Open <http://localhost:3001>:

- Initial snippets show formatted dates: "Jan 15, 2024", "Feb 20, 2024", "Mar 10, 2024"
- Create a new snippet - it gets today's date formatted consistently

### 2. Test utility functions in console

While dev server is running, open browser console and test:

```js
// formatDate is used in the app
// slugify example:
"Hello World!".toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
// → "hello-world"

// truncate example:
"This is a very long text".slice(0, 10) + "..."
// → "This is a ..."
```

The utilities work!

### 3. Build and verify

```bash
turbo build
```

Output:

```
@geniusgarage/utils:build: cache miss, executing 1.234s
@geniusgarage/ui:build: cache hit, replaying outputs
@geniusgarage/web:build: cache hit, replaying outputs
@geniusgarage/snippet-manager:build: cache miss, executing 4.891s

Tasks:    5 successful, 5 total
Cached:   3 cached, 5 total
Time:     5.012s
```

Notice:

- **utils package built** (new dependency)
- **app rebuilt** (imports from utils)
- **web cached** (doesn't use utils yet)

## How shared utils work

Your monorepo now shares utilities:

```
  packages/utils/
  └── src/index.ts        ← Utility functions
          ↓
      formatDate()
          ↓
      apps/snippet-manager
      (imports and uses)
```

**When to create shared packages:**

- **UI components** → `packages/ui` (`Button`, `Card`, etc.)
- **TypeScript config** → `packages/typescript-config` (base.json, nextjs.json)
- **ESLint config** → `packages/eslint-config` (shared linting rules)
- **Utility functions** → `packages/utils` (`formatDate`, `slugify`)
- **Business logic** → `packages/core` (future: snippet validation, etc.)

**Benefits:**

- **Single source** - formatDate defined once, used everywhere
- **Easy testing** - Test pure functions in isolation
- **Reusable** - Any app can import from packages/utils
- **Type-safe** - Full TypeScript support across workspace

## Commit

```bash
git add .
git commit -m "feat(utils): add shared utilities package"
```

## Done-when

Verify shared utilities work:

- [ ] Created `packages/utils/src` directory
- [ ] Added package.json with TypeScript and ESLint config dependencies
- [ ] Created tsconfig.json extending `@geniusgarage/typescript-config/base.json`
- [ ] Implemented `formatDate` function with Intl.DateTimeFormat
- [ ] Implemented `slugify` function with string transformations
- [ ] Implemented truncate function with maxLength check
- [ ] Implemented validateEmail function with regex
- [ ] Exported all functions from src/index.ts
- [ ] Added @geniusgarage/utils dependency to apps/snippet-manager
- [ ] Imported formatDate in apps/snippet-manager/app/page.tsx
- [ ] Changed Snippet interface createdAt to Date type
- [ ] Updated mock data to use Date objects
- [ ] Updated handleCreateSnippet to use Date object
- [ ] Passed formatDate(snippet.createdAt) to SnippetCard
- [ ] Tested in dev and saw formatted dates
- [ ] Built with turbo and saw utils package build

## What's next

You've created three shared packages (ui, config, utils), but Turborepo doesn't know about their dependency relationships. Next lesson: **Update Turbo Pipeline** - configure task dependencies so Turborepo builds packages in the correct order and caches effectively.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
