DEV Community

Harman Panwar
Harman Panwar

Posted on

URL Parameters vs Query Strings in Express.js

URL Parameters vs Query Parameters: A Practical Guide

When building web applications, you'll frequently need to pass data to your server through URLs. The two primary methods for doing this are URL parameters (often called route parameters) and query parameters (query strings). Understanding the difference between these two approaches—and knowing when to use each—is fundamental to building clean, RESTful APIs.

What Are URL Parameters?

URL parameters, also known as route parameters or path parameters, are dynamic segments of a URL path that identify a specific resource. They appear as part of the URL path itself, typically representing an identifier for something specific.

Consider a URL like https://example.com/api/users/42. The 42 in the URL is a URL parameter—it identifies a specific user in your system. The route pattern would be defined as /api/users/:id in Express, where :id is the parameter placeholder that captures whatever value appears in that position of the URL.

URL parameters are ideal when you need to identify a specific resource. They're the backbone of RESTful API design, where URLs represent resources. A blog post with ID 15 should have the URL /posts/15, a product with SKU "ABC123" should have the URL /products/ABC123, and a user's dashboard should have the URL /users/42/dashboard. The parameter identifies the thing you're working with.

In Express, you define URL parameters by prefixing a segment with a colon:

app.get('/users/:id', (req, res) => {
  // The :id captures the value from the URL
  console.log(req.params.id);
});
Enter fullscreen mode Exit fullscreen mode

When someone visits /users/42, the route matches and req.params.id contains "42".

What Are Query Parameters?

Query parameters appear after the question mark (?) in a URL and are used to modify or filter a request. They're optional and don't identify a specific resource. Instead, they provide additional information about what data you want.

Going back to the user example, if you want to get a list of users filtered by some criteria, you'd use query parameters. A URL like https://example.com/api/users?role=admin&status=active uses query parameters to request only users who are both admins and currently active. The question mark marks the beginning of the query string, and parameters are separated by ampersands (&).

Query parameters are perfect for filtering, searching, sorting, and paginating. They're also useful for optional modifications to a request—when you want the same resource but with different presentation or behavior.

In Express, query parameters are available on the req.query object:

app.get('/api/users', (req, res) => {
  // req.query is an object with all query parameters
  console.log(req.query.role);      // "admin"
  console.log(req.query.status);    // "active"
});
Enter fullscreen mode Exit fullscreen mode

Express automatically parses the query string and populates req.query with key-value pairs.

Key Differences Between Them

Understanding the distinction between URL parameters and query parameters comes down to their purpose in your application.

URL parameters identify resources. They're essential to the request—they specify exactly which resource you want to interact with. Without them, the request would target a different resource or fail to match any route. In GET /posts/42, the 42 identifies a single, specific blog post. Remove it, and you're asking for something different (all posts, or no matching route).

Query parameters modify requests. They're optional additions that affect how the server should respond. They filter, sort, paginate, or otherwise adjust the response without changing which resource you're accessing. In GET /posts?category=javascript, you're still asking for posts—but filtered to only those in the JavaScript category.

URL parameters are part of the path. They appear before any question mark and are considered part of the route itself. The route /users/:id matches any user URL.

Query parameters are optional. They always appear after the question mark and are not required for a route to match. The route /posts matches regardless of whether you include query parameters or not.

URL parameters are typically singular identifiers. A URL like /orders/1024 refers to order number 1024. Query parameters are more flexible and can represent multiple optional values.

Accessing URL Parameters in Express

When you define a route with parameter placeholders, Express captures those values and makes them available through req.params. Each parameter you define becomes a property on this object.

Here's a practical example covering common scenarios:

// Simple parameter - get a specific user
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  // Fetch and return the user with this ID
});

// Multiple parameters - nested resources
app.get('/users/:userId/posts/:postId', (req, res) => {
  const userId = req.params.userId;    // "123"
  const postId = req.params.postId;    // "456"
  // Fetch a specific post by a specific user
});

// Optional parameters using two routes
app.get('/users/:id', (req, res) => {
  // GET /users/123
});

app.get('/users', (req, res) => {
  // GET /users
});

// Parameters with specific format (example with regex)
app.get('/messages/:id([0-9]+)', (req, res) => {
  const messageId = req.params.id;
  // Only matches if id is numeric
});
Enter fullscreen mode Exit fullscreen mode

The parameter values are always strings. If you expect a numeric ID and need to work with it as a number, you'll need to convert it:

app.get('/products/:id', (req, res) => {
  const productId = parseInt(req.params.id, 10);

  if (isNaN(productId)) {
    return res.status(400).json({ error: 'Invalid product ID' });
  }

  // Now productId is a number
});
Enter fullscreen mode Exit fullscreen mode

You can also use optional parameters by defining multiple routes or by making the parameter optional with a regex pattern that allows empty strings, but it's often cleaner to just have separate routes for different cases.

Accessing Query Parameters in Express

Query parameters are automatically parsed by Express and made available on the req.query object. Each key-value pair in the query string becomes a property on this object.

Let's explore practical scenarios for working with query parameters:

// Basic query parameters
app.get('/api/search', (req, res) => {
  const query = req.query.q;  // From ?q=searchterm
  const page = req.query.page;  // From ?page=2
});

// Multiple query parameters
app.get('/api/users', (req, res) => {
  const { role, status, sort, order } = req.query;

  // Build database query based on filters
  let results = db.users;

  if (role) {
    results = results.filter(u => u.role === role);
  }

  if (status) {
    results = results.filter(u => u.status === status);
  }

  // Sort results
  if (sort) {
    results.sort((a, b) => {
      if (order === 'desc') {
        return b[sort] - a[sort];
      }
      return a[sort] - b[sort];
    });
  }

  res.json(results);
});
Enter fullscreen mode Exit fullscreen mode

For a request like /api/users?role=admin&status=active&sort=createdAt&order=desc, the req.query object would be:

{
  role: 'admin',
  status: 'active',
  sort: 'createdAt',
  order: 'desc'
}
Enter fullscreen mode Exit fullscreen mode

Query parameters are always strings. For boolean or numeric values, you'll need to convert them:

app.get('/api/posts', (req, res) => {
  const page = parseInt(req.query.page, 10) || 1;
  const limit = parseInt(req.query.limit, 10) || 10;
  const published = req.query.published === 'true';
});
Enter fullscreen mode Exit fullscreen mode

When a query parameter is present but has no value (like /api/search?q), Express provides an empty string. When it's absent entirely, the property is undefined.

When to Use URL Parameters vs Query Parameters

Choosing between URL parameters and query parameters isn't always obvious. Here are practical guidelines for making the decision.

Use URL parameters when identifying a specific resource. If you're getting, updating, or deleting a particular item, use a URL parameter to identify it. The endpoint /users/42 clearly identifies user 42. The endpoint /posts/15/comments/8 identifies comment 8 on post 15.

Use query parameters for filtering lists. When fetching a collection of resources, use query parameters to filter what items are returned. /posts?category=javascript filters posts to only JavaScript-related ones. /products?inStock=true&minPrice=50 adds multiple filters.

Use query parameters for sorting. When you want the client to specify the sort order, use query parameters. /articles?sort=publishedAt&order=desc clearly communicates the desired sort order through the query string.

Use query parameters for pagination. Pagination naturally fits query parameters since page and limit are filters on the result set. /api/users?page=2&limit=20 requests the second page with 20 items per page.

Use query parameters for optional behavior. When the same endpoint needs to behave differently based on optional flags, query parameters work well. /api/report?format=pdf&includeCharts=true modifies how the report is generated without changing which report is being requested.

Use URL parameters for required filters on specific resources. If you have an endpoint that only makes sense with a specific identifier, that identifier belongs in the URL. /users/42/settings identifies the settings resource for user 42.

Practical Examples

Let's walk through real-world scenarios that demonstrate when to use each approach.

Example 1: User Profile

Getting a user's profile is a perfect case for URL parameters:

// Route: GET /users/:id
// URL: /users/42
// Usage: Navigate to a specific user's profile page

app.get('/users/:id', async (req, res) => {
  const userId = req.params.id;
  const user = await db.users.findById(userId);

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  res.json(user);
});
Enter fullscreen mode Exit fullscreen mode

The user ID is a resource identifier. The URL itself identifies the resource you're requesting.

Example 2: Search with Filters

When searching through content with multiple filter options, query parameters are ideal:

// Route: GET /api/blogs
// URL: /api/blogs?q=javascript&category=tutorial&sort=date&order=desc
// Usage: Search and filter blog posts

app.get('/api/blogs', async (req, res) => {
  const { q, category, tag, sort, order, page, limit } = req.query;

  // Build the search query
  let query = db.blogs.find();

  // Text search
  if (q) {
    query = query.where('title').contains(q);
  }

  // Category filter
  if (category) {
    query = query.where('category', category);
  }

  // Tag filter
  if (tag) {
    query = query.where('tags').contains(tag);
  }

  // Sorting
  const sortField = sort || 'createdAt';
  const sortDirection = order === 'desc' ? 'desc' : 'asc';
  query = query.orderBy(sortField, sortDirection);

  // Pagination
  const pageNum = parseInt(page, 10) || 1;
  const limitNum = parseInt(limit, 10) || 10;
  const offset = (pageNum - 1) * limitNum;

  const blogs = await query.skip(offset).limit(limitNum).exec();
  const total = await db.blogs.countDocuments(query);

  res.json({
    blogs,
    pagination: {
      page: pageNum,
      limit: limitNum,
      total,
      pages: Math.ceil(total / limitNum)
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

This single endpoint can handle a wide variety of search and filter scenarios—all through query parameters. A user can search without filtering (?q=javascript), filter without searching, sort by different fields, or paginate through results.

Example 3: Photo Gallery

A photo gallery application might use both URL and query parameters together:

// Route for a specific album
// GET /albums/:albumId
// URL: /albums/15
// Shows all photos in album 15

app.get('/albums/:albumId', async (req, res) => {
  const album = await db.albums.findById(req.params.albumId);
  res.json(album);
});

// Route for photos in an album with filters
// GET /albums/:albumId/photos
// URL: /albums/15/photos?orientation=landscape&size=large&sort=date
// Shows filtered photos from album 15

app.get('/albums/:albumId/photos', async (req, res) => {
  const { orientation, size, sort, order } = req.query;

  let photos = await db.photos
    .where('albumId', req.params.albumId);

  if (orientation) {
    photos = photos.where('orientation', orientation);
  }

  if (size) {
    photos = photos.where('size', size);
  }

  if (sort) {
    photos = photos.orderBy(sort, order || 'asc');
  }

  res.json(photos);
});
Enter fullscreen mode Exit fullscreen mode

Here, the URL parameter albumId identifies the resource (the album), while query parameters filter how we view that resource (which photos, how sorted).

Example 4: API with Multiple Filter Combinations

Consider an e-commerce product listing with complex filtering:

// GET /api/products
// URL: /api/products?category=electronics&brand=apple&minPrice=500&maxPrice=2000&inStock=true&sort=price&order=asc

app.get('/api/products', async (req, res) => {
  const {
    category,
    brand,
    minPrice,
    maxPrice,
    inStock,
    sort,
    order,
    featured,
    page,
    limit
  } = req.query;

  let products = db.products.find();

  // Category filter
  if (category) {
    products = products.where('category', category);
  }

  // Brand filter
  if (brand) {
    products = products.where('brand', brand);
  }

  // Price range filter
  if (minPrice) {
    products = products.where('price').gte(parseFloat(minPrice));
  }
  if (maxPrice) {
    products = products.where('price').lte(parseFloat(maxPrice));
  }

  // Stock filter
  if (inStock === 'true') {
    products = products.where('stock').gt(0);
  }

  // Featured filter
  if (featured === 'true') {
    products = products.where('featured', true);
  }

  // Sorting
  if (sort) {
    const sortOrder = order === 'desc' ? 'desc' : 'asc';
    products = products.orderBy(sort, sortOrder);
  }

  // Pagination
  const pageNum = Math.max(1, parseInt(page, 10) || 1);
  const limitNum = Math.min(100, Math.max(1, parseInt(limit, 10) || 20));
  const offset = (pageNum - 1) * limitNum;

  const total = await products.count();
  const results = await products.skip(offset).limit(limitNum);

  res.json({
    products: results,
    meta: {
      page: pageNum,
      limit: limitNum,
      total,
      totalPages: Math.ceil(total / limitNum)
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

This single endpoint handles an enormous variety of product browsing scenarios, all through query parameters. The route /api/products never changes, but the query string determines exactly what products are returned.

Suggestions for Working with Parameters

Be explicit about expected types. URL parameters and query parameters always come as strings. Validate and convert them early in your route handler:

app.get('/posts/:id', (req, res, next) => {
  const id = parseInt(req.params.id, 10);

  if (isNaN(id) || id < 1) {
    return res.status(400).json({ error: 'Invalid ID format' });
  }

  req.params.id = id;
  next();
}, (req, res) => {
  // Now req.params.id is a valid number
});
Enter fullscreen mode Exit fullscreen mode

Sanitize user input. Never trust user input, even from URL parameters. Always validate and sanitize before using in database queries to prevent injection attacks.

Use consistent naming conventions. Establish naming conventions for query parameters across your API. If you use page for pagination in one endpoint, don't use p or pageNum in another. Consistency makes your API predictable and easier to use.

Document optional query parameters. Unlike URL parameters, which are necessary for the route to match, query parameters are optional. Make sure to document which query parameters your endpoints accept and what values they expect.

Consider default values. When query parameters are missing, provide sensible defaults:

app.get('/api/content', (req, res) => {
  const page = parseInt(req.query.page, 10) || 1;
  const limit = parseInt(req.query.limit, 10) || 20;
  const sort = req.query.sort || 'createdAt';

  // ... rest of the handler
});
Enter fullscreen mode Exit fullscreen mode

Use array syntax for multiple values. When you need to accept multiple values for the same parameter, use array syntax in the query string:

// URL: /api/posts?tags=javascript&tags=nodejs&tags=express
// req.query.tags becomes ['javascript', 'nodejs', 'express']

app.get('/api/posts', (req, res) => {
  const tags = req.query.tags;  // Array of tag names

  if (tags && tags.length > 0) {
    // Filter by multiple tags
  }
});
Enter fullscreen mode Exit fullscreen mode

Keep URLs readable. URL parameters should result in clean, readable URLs. While ?id=42 works, /users/42 is more intuitive and follows REST conventions. Query parameters keep the base URL clean while still allowing complex filtering.

Quick Reference

Here's a quick comparison to help you decide:

Use Case Parameter Type
Get specific user by ID URL parameter (/users/:id)
Get all users No parameters
Filter users by role Query parameter (?role=admin)
Get user's posts URL parameter (/users/:id/posts)
Sort posts by date Query parameter (?sort=date)
Paginate results Query parameter (?page=2&limit=20)
Search posts Query parameter (?q=searchterm)
Update specific user URL parameter (/users/:id)
Get active products only Query parameter (?status=active)

Conclusion

URL parameters and query parameters serve different but complementary purposes in your Express applications. URL parameters identify resources—they're essential to the request and tell the server what you're working with. Query parameters modify requests—they're optional filters and options that customize how the server responds.

The key is to think about what your URL represents. If it's a resource identifier, use a URL parameter. If it's modifying or filtering that resource, use a query parameter. Most complex APIs will use both together—the URL identifies the resource, and query parameters provide options for how to view or interact with it.

By following REST conventions and using these parameter types appropriately, you'll build APIs that are intuitive, predictable, and easy to use. Your URLs will clearly communicate what resources exist and how they can be filtered, sorted, and paginated.

Top comments (0)