<!-- Markdown mirror of https://justscale.sh/docs/http/path-parameters -->

# Path Parameters

Extract typed parameters from URL paths

Path parameters allow you to capture dynamic segments from URLs. JustScale provides compile-time type safety for path parameters extracted from route patterns.

## Basic Path Parameters

Define path parameters using the `:paramName` syntax:

src/controllers/users.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';

export const UsersController = createController('/users', {
  routes: () => ({
    getOne: Get('/:id').handle(({ params, res }) => {
      // params.id is typed as string and guaranteed to exist
      const userId = params.id;
      res.json({ userId });
    }),
  }),
});
```

When a request comes in to `/users/123`, the `params` object will be `{ id: '123' }`.

## Type Safety

TypeScript extracts the parameter names from your path pattern and makes them available in the `params` object with type safety:

src/controllers/posts.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';

export const PostsController = createController('/posts', {
  routes: () => ({
    getComment: Get('/:postId/comments/:commentId').handle(({ params, res }) => {
      // TypeScript knows params has both postId and commentId
      const { postId, commentId } = params;

      // This would be a TypeScript error:
      // const invalid = params.nonexistent; // Error!

      res.json({ postId, commentId });
    }),
  }),
});
```

## Multiple Path Parameters

You can have multiple parameters in a single route:

src/controllers/orgs.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';

export const OrgsController = createController('/orgs', {
  routes: () => ({
    getMember: Get('/:orgId/teams/:teamId/members/:userId').handle(({ params, res }) => {
      const { orgId, teamId, userId } = params;
      res.json({ orgId, teamId, userId });
    }),
  }),
});
```

Example request:

Bash

```bash
curl http://localhost:3000/orgs/acme/teams/engineering/members/alice
# { "orgId": "acme", "teamId": "engineering", "userId": "alice" }
```

## Parameter Constraints

### All Parameters Are Strings

Path parameters are always extracted as strings. If you need a number, parse it:

src/controllers/items.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';

export const ItemsController = createController('/items', {
  routes: () => ({
    getOne: Get('/:itemId').handle(({ params, res }) => {
      const itemId = parseInt(params.itemId, 10);

      if (isNaN(itemId)) {
        res.error('Invalid item ID', 400);
        return;
      }

      res.json({ itemId });
    }),
  }),
});
```

### Using populate() for Type Conversion

For entities, use the `populate` middleware with a `transform`function to convert the ID:

src/controllers/posts.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';
import { populate } from '@justscale/http';
import { PostService } from '../services/post-service';

export const PostsController = createController('/posts', {
  inject: { posts: PostService },

  routes: (services) => ({
    getOne: Get('/:postId')
      .use(populate(
        services.posts,
        'post',
        'postId',
        Post.ref,
        { transform: id => parseInt(id, 10) }
      ))
      .handle(({ post, res }) => {
        // post is already fetched with numeric ID
        res.json({ post });
      }),
  }),
});
```

## URL Encoding

Parameters are automatically URL-decoded:

src/controllers/search.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';

export const SearchController = createController('/search', {
  routes: () => ({
    search: Get('/:query').handle(({ params, res }) => {
      // Request: /search/hello%20world
      console.log(params.query); // "hello world" (decoded)
      res.json({ query: params.query });
    }),
  }),
});
```

## Validation

While path parameters are type-safe at compile time, you should validate them at runtime:

src/controllers/users.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';
import { validateParams } from '../middleware/validate-params';
import { ParamsSchema } from '../schemas/params';

export const UsersController = createController('/users', {
  routes: () => ({
    getOne: Get('/:id')
      .use(validateParams(ParamsSchema))
      .handle(({ params, res }) => {
        // params.id is guaranteed to be a UUID string
        res.json({ userId: params.id });
      }),
  }),
});
```

## Common Patterns

### UUID Parameters

src/controllers/users.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';

export const UsersController = createController('/users', {
  routes: () => ({
    getOne: Get('/:userId').handle(({ params, res }) => {
      // Validate UUID format
      const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
      if (!uuidRegex.test(params.userId)) {
        res.error('Invalid user ID format', 400);
        return;
      }
      res.json({ userId: params.userId });
    }),
  }),
});
```

### Slug Parameters

src/controllers/blog.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get } from '@justscale/http';

export const BlogController = createController('/blog', {
  routes: () => ({
    getPost: Get('/:slug').handle(({ params, res }) => {
      const slug = params.slug;
      // slug might be: "my-first-post" or "hello-world"
      res.json({ slug });
    }),
  }),
});
```

### Nested Resources

src/controllers/posts.tsTypeScript

```typescript
import { createController } from '@justscale/core';
import { Get, Delete } from '@justscale/http';
import { PostService } from '../services/post-service';

export const PostsController = createController('/posts', {
  inject: { posts: PostService },
  routes: (services) => ({
    // GET /posts/:postId
    getOne: Get('/:postId').handle(({ params, res }) => {
      res.json({ postId: params.postId });
    }),

    // GET /posts/:postId/comments/:commentId
    getComment: Get('/:postId/comments/:commentId').handle(({ params, res }) => {
      res.json({ postId: params.postId, commentId: params.commentId });
    }),
  }),
});
```

## Non-Matching Routes

ℹ️Info

If a request doesn't match any route pattern, JustScale returns a 404 automatically.

Bash

```bash
# Route defined: GET /users/:id
curl http://localhost:3000/users/123     # Matches
curl http://localhost:3000/users         # 404 Not Found
curl http://localhost:3000/users/123/foo # 404 Not Found
```

## Next Steps

- Query & Body
- Response Types
- Validation
