Template Literal Types

A template literal type puts the backtick syntax of template strings (Template Literals) in a type position: with type Version = "v1" | "v2", ` /api/${Version} is "/api/v1" | "/api/v2", and several unions in one template multiply into every combination. Four helpers built into the compiler, Uppercase, Lowercase, Capitalize and Uncapitalize, transform the strings. Inside a conditional type, a template with infer` parses a string literal, so a router can read parameter names straight out of a path:

Route parameters parsed from the pathTypeScript
type ParamNames<Path extends string> =
  Path extends `${string}:${infer Name}/${infer Rest}` ? Name | ParamNames<Rest>
  : Path extends `${string}:${infer Name}` ? Name
  : never;
type RouteParams<Path extends string> = Record<ParamNames<Path>, string>;
function buildUrl<P extends string>(path: P, params: RouteParams<P>): string {
  return path.replace(/:(\w+)/g, (_, name: ParamNames<P>) => encodeURIComponent(params[name]));
}
console.log(buildUrl("/users/:userId/posts/:postId", { userId: "42", postId: "7" }));
buildUrl("/users/:userId/posts/:postId", { userId: "42" });
Output
$ npx tsc --noEmit
routes.ts(11,42): error TS2741: Property 'postId' is missing in type '{ userId: string; }' but
  required in type 'RouteParams<"/users/:userId/posts/:postId">'.
$ node routes.ts
/users/42/posts/7

For "/users/:userId/posts/:postId" the first branch matches Name = "userId" and recurses on "posts/:postId", whose second branch yields "postId". The Express type definitions (@types/express-serve-static-core) type req.params the same way.