Skip to content

Instantly share code, notes, and snippets.

@gtrabanco
Created February 16, 2022 11:47
Show Gist options
  • Save gtrabanco/84cde5075458541517b5edb8d1c9f298 to your computer and use it in GitHub Desktop.
Save gtrabanco/84cde5075458541517b5edb8d1c9f298 to your computer and use it in GitHub Desktop.
Parse urls like express router or react router to accept params in url and replace params in url
function getURLParams(urlWithParams) {
const paramsURLParts = urlWithParams.split('/');
let params = {};
for (const index in paramsURLParts) {
let parsedParam;
let part = paramsURLParts.at(index);
if (part.match(/:([\w_]{1}[\w0-9-_]+)/i)) {
part = part.replace(':', '');
}
// Optional
parsedParam = part.match(/^\[([.]{2,3})?([\w_]{1}[\w0-9-_]+)\]$/i);
if (parsedParam) {
const lastGetOtherPars =
parseInt(index) === paramsURLParts.length - 1 &&
parsedParam[1] !== 'undefined';
params[parsedParam[2].toLowerCase()] = {
mandatory: false,
index: parseInt(index),
lastGetOtherPars,
};
continue;
}
// Mandatory Param
parsedParam = part.match(/^([.]{2,3})?([\w_]{1}[\w0-9-_]+)$/i);
if (parsedParam) {
const lastGetOtherPars =
parseInt(index) === paramsURLParts.length - 1 &&
parsedParam[1] !== 'undefined';
params[parsedParam[2].toLowerCase()] = {
mandatory: true,
index: parseInt(index),
lastGetOtherPars,
};
continue;
}
}
return params;
}
function getURLParamsValues(url, params) {
let result = {};
const urlParts = url.split('/');
for (const [param, paramInfo] of Object.entries(params)) {
if (paramInfo.mandatory && urlParts.at(paramInfo.index)?.length === 0)
throw new Error(
`Mandatory param name "${param}" is not defined in url: ${url}`
);
if (paramInfo.lastGetOtherPars) {
result[param] =
urlParts
.slice(paramInfo.index)
.map((item) => decodeURIComponent(item)) ?? null;
} else {
result[param] = decodeURIComponent(urlParts.at(paramInfo.index)) ?? null;
}
}
return result ?? {};
}
// Transform url like http://:host/custom/path/:id to be completed with object
// { host: "localhost", id: 7 }
// Result should be: http://localhost/custom/path/7
export function transformURLWithParamsValues(urlWithParams, paramsValues) {
let url = urlWithParams.split('/');
const params = getURLParams(urlWithParams);
for (const [param, paramInfo] of Object.entries(params)) {
if (paramInfo.mandatory && url.at(paramInfo.index)?.length === 0)
throw new Error(
`Mandatory param name "${param}" is not defined in url: ${url}`
);
if (paramsValues[param]) url[paramInfo.index] = paramsValues[param];
}
url = url
.map((item, key) => (key in [0, 1, 2] ? item : encodeURIComponent(item)))
.join('/');
if (`${url}/`.match(/\/:([.]{2,3})?([\w_]{1}[\w0-9-_]+)\//i)) {
throw new Error(
`There is undefined param when susbstituting ${urlWithParams}`
);
}
return url;
}
// const getUrlProps = parseURLWithParams('http://:host/:app/[:other]
// console.log(getUrlProps('http://localhost/blog/some/more/path')
// Should print something similar to: { host: "localhost", app: "blog", other: ["some", "more", "path"]}
export default function parseURLWithParams(paramsURL = '') {
const params = getURLParams(paramsURL);
return (url) => {
return getURLParamsValues(url, params);
};
}
@gtrabanco
Copy link
Author

import { transformURLWithParamsValues } from './parseURLWithParams.mjs';

export const HTTP_REQUEST_METHOD = {
  GET: 'GET',
  POST: 'POST',
  PUT: 'PUT',
  DELETE: 'DELETE',
  UPDATE: 'UPDATE',
};

// You can define baseURL as routes with params in express, react... etc.
// Note that last param :id will case
// baseURL = 'https://pokeapi.co/api/:apiVersion/:path/[:id]'
// createRestApi(baseURL).pokemon({ apiVersion: 'v2'})
export function createRestApi(baseURL) {
  return new Proxy(
    {},
    {
      get: (target, path) => {
        return async function ({ urlParams = {}, init = {} } = {}) {
          let stringURL;
          const fetchProps = {
            method: HTTP_REQUEST_METHOD.GET,
            cache: 'default',
            redirect: 'follow',
            ...init,
          };

          if (!(baseURL instanceof URL)) {
            stringURL = transformURLWithParamsValues(baseURL, {
              path,
              ...urlParams,
            });
          } else {
            baseURL.pathname = path;
            stringURL = baseURL.toString();
          }

          try {
            const url = new URL(stringURL);
            const response = await fetch(url, fetchProps);
            if (!response.ok) throw new Error('Fetch could not be completed');

            return await response;
          } catch (error) {
            return Promise.reject(error);
          }
        };
      },
    }
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment