First let's go through what your regex is doing, then we can fix it.
Your regex:
^ - the beginning of the string
(.*) - match any character 0 or more times - as many times as possible (greedy)
[?]? - match `?` 0 or 1 times
(.*) - match any character 0 or more times - as many times as possible (greedy)
$ - the end of the string
Really the main problem here is that the first capturing group captures as many times as possible, so that'll always match the entire url. We can make that non-greedy by using .*?
, so we end up with ^(.*?)[?]?(.*)$
. However now we end up with the problem that the last capturing group captures the entire url - we could make this non-greedy but then it wouldn't match any characters at all. Instead, we should make sure that this group will only capture when ?
is present, so we can make [?]?
non-optional, move it into the next capturing group, and make the last group optional, like this: ([?](.*))?
. Whilst we're at it, we might as well use \?
instead of [?]
and we end up with ^(.*?)(\?(.*))?$
. This works, as the $
signifies that we want to capture right up to the end. With this we'd need to use $3
instead of $2
as $2
now contains ?
as well when replacing, so we can use a non-capturing group to eliminate that problem.
So our final regex is /(.*?)(?:\?(.*))?/g
.
Your final code will look like this:
let url = "https://test1.com/path?query1=value1"
console.log(url.replaceAll(/^(.*?)(?:\?(.*))?$/g,"$1?newquery=newvalue&$2"))
url = "https://test1.com/path"
console.log(url.replaceAll(/^(.*?)(?:\?(.*))?$/g,"$1?newquery=newvalue&$2"))