name users-service description This skill should be used when the user asks to "add an endpoint to users", "create a route in users service", "write tests for users", "test users service", "add auth to a route", "work on users API", or mentions the users service, Fastify routes, or user management functionality.
Users Service Development Skill
This skill provides patterns and workflows for developing the Users Service, a Fastify-based REST API at services/users/ that handles user management with Auth0 authentication and Prisma ORM.
Service Overview
Location : services/users/
Framework : Fastify v5 with TypeScript
Database : PostgreSQL via Prisma
Auth : Auth0 JWT verification with jose
Port : 3001 (API docs at http://localhost:3001/docs )
Key Files
File Purpose src/app.tsFastify app setup, plugin registration src/routes/users.tsUser CRUD endpoints and auth endpoints src/services/user.tsBusiness logic (Prisma operations) src/schemas/index.tsOpenAPI schema definitions prisma/schema.prismaDatabase schema
Adding New Endpoints
Route Structure Pattern
Follow this pattern when adding new routes to src/routes/users.ts:
fastify.get <{
Params : { id : string };
Querystring : { page ?: string };
Body : CreateUserRequest ;
Reply : ApiResponse <User > | ApiError ;
}>(
"/endpoint-path" ,
{
preHandler : verifyAuth,
schema : {
summary : "Short action description" ,
operationId : "uniqueOperationName" ,
description : "Detailed description for API docs." ,
tags : ["Users" ],
security : [{ bearerAuth : [] }],
params : { },
body : { },
response : {
200 : {
description : "Success case" ,
type : "object" ,
properties : {
data : { $ref : "User#" },
},
},
404 : { $ref : "Error#" },
},
},
},
async (request, reply) => {
}
);
Adding a Protected Endpoint
Protected endpoints require JWT authentication. Add preHandler: verifyAuth and security schema:
fastify.get <{ Reply : ApiResponse <User > }>(
"/me/profile" ,
{
preHandler : verifyAuth,
schema : {
summary : "Get extended profile" ,
operationId : "getExtendedProfile" ,
tags : ["Users" ],
security : [{ bearerAuth : [] }],
response : {
200 : { },
401 : { $ref : "Error#" },
},
},
},
async (request, reply) => {
const authUser = request.user ;
if (!authUser?.email ) {
return reply.code (401 ).send ({
error : "Unauthorized" ,
message : "Authentication required" ,
statusCode : 401 ,
});
}
}
);
Adding Service Methods
Add business logic to src/services/user.ts. Always use mapPrismaUser() to convert Prisma types:
async getByUsername (username : string ): Promise <User | null > {
const user = await prisma.user .findFirst ({
where : { username },
});
return user ? mapPrismaUser (user) : null ;
},
Shared Types
Import types from @mbe/types:
import type {
User ,
CreateUserRequest ,
UpdateUserRequest ,
ApiResponse ,
ApiError ,
PaginatedResponse ,
} from "@mbe/types" ;
Testing
Test Commands
cd services/users
pnpm test
pnpm test :watch
pnpm test :coverage
npx vitest run src/routes/users.test.ts
npx vitest --grep "GET /v1/users"
Test Structure Pattern
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" ;
import type { FastifyInstance } from "fastify" ;
import { buildApp } from "../app.js" ;
import { userService } from "../services/user.js" ;
vi.mock ("../services/user.js" , () => ({
userService : {
list : vi.fn (),
getById : vi.fn (),
create : vi.fn (),
update : vi.fn (),
delete : vi.fn (),
},
}));
describe ("User Routes" , () => {
let app : FastifyInstance ;
beforeEach (async () => {
app = await buildApp ({ logger : false });
await app.ready ();
});
afterEach (async () => {
await app.close ();
vi.clearAllMocks ();
});
describe ("GET /v1/users" , {
( , () => {
mockUsers = [
{ : , : , },
];
vi. (userService. ). ({
: mockUsers,
: { : , : , : , },
});
response = app. ({
: ,
: ,
});
(response. ). ( );
( . (response. )). ({
: mockUsers,
: expect. ( ),
});
});
});
});
Testing Protected Routes
Mock the auth layer or test 401 responses:
it ("should return 401 without auth header" , async () => {
const response = await app.inject ({
method : "GET" ,
url : "/v1/users/me" ,
});
expect (response.statusCode ).toBe (401 );
});
Auth0 Integration
Environment Variables
AUTH_AUTHORITY=https://dev-ytbgmz5ls3wh4xdx.us.auth0.com
AUTH_AUDIENCE=https://api.mattbutlerengineering.com
How Auth Works
Client gets JWT from Auth0
Client sends Authorization: Bearer <token> header
verifyAuth preHandler validates token via JWKS
request.user populated with user info from JWT claims
JWT Payload Structure
interface JWTPayload {
sub : string ;
email : string ;
email_verified : boolean ;
name ?: string ;
picture ?: string ;
}
Accessing Auth User
In protected routes, access the authenticated user:
async (request, reply) => {
const authUser = request.user ;
}
Database Schema
Current User model in prisma/schema.prisma:
model User {
id String @id @default(cuid())
email String @unique
name String?
picture String?
emailVerified Boolean @default(false)
preferences Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
For schema changes, use the prisma-migrations skill.
API Endpoints Reference
Method Path Auth Description GET /v1/usersNo List users (paginated) GET /v1/users/:idNo Get user by ID POST /v1/usersNo Create user PATCH /v1/users/:idNo Update user DELETE /v1/users/:idNo Delete user GET /v1/users/meYes Get current user (auto-creates) PATCH /v1/users/me/preferencesYes Update preferences GET /healthNo Health check
Common Development Tasks
Start Development Server
cd services/users
pnpm dev
Check Code Quality
pnpm lint
pnpm typecheck
Open API Docs
Navigate to http://localhost:3001/docs for interactive Scalar API documentation.
Database Operations
pnpm db:studio
pnpm db:push
pnpm db:migrate
Error Response Format
All errors follow this structure:
{
error : "Not Found" ,
message : "User not found" ,
statusCode : 404
}
Quick Checklist for New Endpoints
Add TypeScript types for Params/Body/Reply
Include OpenAPI schema with summary, description, tags
Add preHandler: verifyAuth if protected
Add security: [{ bearerAuth: [] }] if protected
Handle all error cases (400, 401, 404, 500)
Add service method if new business logic needed
Write tests with mocked service layer
Run pnpm test and pnpm typecheck