ASAgriSense/Docs/New Feature Package

Development

New Feature Package

Step-by-step guide for adding a new SMS feature module. Use feature-farmer-registry and feature-farmer-registry-web as reference examples — they are the most complete implementations.

Decide on placement#

If your code…Place it in
Accesses the database or calls external APIsbe-packages/feature-<domain>
Renders React UI for a specific featurefe-packages/feature-<domain>-web
Defines types/schemas shared between API and webshared/<domain>-contracts
Is truly reusable cross-domain validationshared/validation

Implementation steps#

  1. 1

    Create the backend package

    Create the directory and required files:

    bash
    mkdir -p be-packages/feature-<domain>/src/{db,schema,services,routes}
    touch be-packages/feature-<domain>/src/index.ts

    Minimum package.json:

    json
    {
      "name": "@repo/feature-<domain>",
      "version": "0.1.0",
      "type": "module",
      "private": true,
      "main": "./src/index.ts",
      "exports": {
        ".": "./src/index.ts"
      },
      "dependencies": {
        "@repo/validation": "*"
      },
      "devDependencies": {
        "@repo/typescript-config": "*",
        "@repo/eslint-config": "*",
        "typescript": "5.9.2",
        "vitest": "^3.2.4"
      }
    }
  2. 2

    Define Zod schemas first

    In src/schema/, define validation schemas before writing any service logic. Infer TypeScript types from schemas.

    typescript
    // src/schema/my-entity.schema.ts
    import { z } from 'zod';
    
    export const CreateMyEntitySchema = z.object({
      name: z.string().min(1),
      orgId: z.string().uuid(),
    });
    
    export type CreateMyEntityInput = z.infer<typeof CreateMyEntitySchema>;
  3. 3

    Write Drizzle ORM schema

    In src/db/, define the Drizzle table schema and generate a migration:

    bash
    npm run db:generate:<domain>
  4. 4

    Implement service functions

    In src/services/, write pure service functions. No HTTP — just data access via Drizzle + business logic. Services must accept orgId and scope all queries to it.

  5. 5

    Write route handlers

    In src/routes/, write Hono route handlers. Routes are thin: validate input → call service → return result.

    typescript
    import { zValidator } from '@hono/zod-validator';
    import { Hono } from 'hono';
    import { CreateMyEntitySchema } from '../schema/my-entity.schema';
    import { createMyEntity } from '../services/my-entity.service';
    
    export const myEntityRoutes = new Hono()
      .post('/', zValidator('json', CreateMyEntitySchema), async (c) => {
        const { orgId } = c.get('auth');
        const input = c.req.valid('json');
        const entity = await createMyEntity({ ...input, orgId });
        return c.json(entity, 201);
      });
  6. 6

    Export from index.ts

    typescript
    // src/index.ts — only export what consumers need
    export { myEntityRoutes } from './routes/my-entity.routes';
    export { CreateMyEntitySchema, type CreateMyEntityInput } from './schema/my-entity.schema';
  7. 7

    Wire into apps/api

    Add the new package to apps/api/package.json dependencies and mount the routes in apps/api/src/index.ts:

    typescript
    import { myEntityRoutes } from '@repo/feature-<domain>';
    app.route('/api/my-entities', myEntityRoutes);
  8. 8

    Create the frontend package

    Create fe-packages/feature-<domain>-web/ with the same package.json pattern, using @repo/feature-farmer-registry-web as a reference. Add SWR hooks, API client helpers, and React components.

  9. 9

    Add @source for Tailwind

    In apps/web/app/globals.css, add a source entry so Tailwind v4 scans the new package's classes:

    css
    @source "../../../fe-packages/feature-<domain>-web/src/**/*.{ts,tsx}";
  10. 10

    Verify and test

    bash
    # From the repo root
    npm run typecheck
    npm run lint
    npm run test
    npm run build

Optional: shared contracts package#

If the API response types need to be consumed by both the frontend and the API (e.g. a complex response structure), extract them into shared/<domain>-contracts/. See shared/dashboard-contracts/ and shared/eudr-compliance-contracts/ as examples.

Checklist before merging#

  • No app-specific business logic in route pages.
  • No deep imports outside documented package exports.
  • npm run typecheck passes for all touched workspaces.
  • npm run lint passes with zero warnings.
  • At least one service unit test and one route smoke test are included.
  • New API routes have Zod validation on all inputs.
  • New fe-package is added as a @source in apps/web/app/globals.css.
Reference implementation
be-packages/feature-farmer-registry and fe-packages/feature-farmer-registry-web are the most complete and well-tested packages in the codebase. Use them as the reference for structure, patterns, and naming.