Hello, I played around with basic-webserver and tried to find out if I can get it connected to a postgres database.
I found this library https://github.com/agu-z/roc-pg/ but it hasn't been touched for over a year, so it doesn't work with the latest roc version. I tried to make it work but I am completely stuck.
First, I looked into rewriting that library with the new syntax but I am lost given the lack of documentation. Then I looked into rewriting it from scratch but I just lack some Roc ecosystem basics. For example, how do I express the fact that this library requires the app using it to have access to Tcp sockets, like in basic-cli?
The only point of reference I have is https://www.roc-lang.org/docs/main/langref/modules/ but it lacks the information or guidance I would need. Does anyone have pointers? Or a library I can use as a reference?
Sorry, I meant to post this into #beginners
This could be a good reference: https://github.com/niclas-ahden/roc-pg :)
Here's a list of packages, which can be really helpful! https://github.com/lukewilliamboswell/roc-awesome
how do I express the fact that this library requires the app using it to have access to Tcp sockets, like in basic-cli?
I've been doing the following for my packages: https://github.com/niclas-ahden/roc-pg/blob/main/examples/query.roc#L18
You specify a number of effects that the package needs and the user passes those in. That way you're never tied to a certain platform. Users can use your package as long as they can provide the required effects :thumbs_up:
This topic was moved here from #contributing > How to (re)write a library? by Anton.
@Niclas Ahden
Thanks for the reference! I looked into it and have some thoughts. Your version of roc-pg depends on your own fork of basic-cli because you implemented connection pooling on top of it. I am not sure if that's the best approach for anyone other than you because ideally, people (myself included) only want to depend on official roc-lang platforms.
This is a tough nut to crack in my brain. I'd love to be able to write a package and say "this only works with basic-cli" and be able to use the code there, but I get that from an ecosystem perspective this is not desirable because it causes fragmentation.
Indeed, to each their own! :)
I was experimenting with connection pooling and am probably going to use that once I start getting my real apps moved over to the new compiler, so that's why it's there. I'm probably going to maintain my own fork of basic-cli for the foreseeable future, because I'll want things in there which _shouldn't_ go in the official one (it's supposed to be "basic", and I'm moving a bit beyond that).
But I am also not sure if passing all effects to the library is the way to go. When I imagine a backend with many different libraries (for database, Redis, SDKs for third-party services, etc.) it seems pretty annoying to have to wire all the code/effects from Tcp into them each time.
I wonder if it would make sense to externalize Tcp (or something similar that represents a bi-directional communication channel) into a separate package similar to how roc-lang/http does so for Request and Response. Then libraries could be written around that and the platform / app would only have to take care of providing the tcp socket.
George said:
But I am also not sure if passing all effects to the library is the way to go. When I imagine a backend with many different libraries (for database, Redis, SDKs for third-party services, etc.) it seems pretty annoying to have to wire all the code/effects from
Tcpinto them each time.
I think that's one of the experiments the Roc community will run, and we'll see how it plays out. There are strong pros of passing in all effects of course, such as: security, visibility, patching up API differences if your platform isn't exactly the same shape, the ability to modify effects before you pass them to the package etc.
However, sometimes it'd be nice to be able to just assume that you have effects available and write your package as so, then the user could pass in e.g. the whole platform and it'll only work if the required API is there (e.g. if a package uses File.read! and Http.get! then those two have to exist with the correct shape, while we disregard everything else).
George said:
I wonder if it would make sense to externalize
Tcp(or something similar that represents a bi-directional communication channel) into a separate package similar to howroc-lang/httpdoes so forRequestandResponse. Then libraries could be written around that and the platform / app would only have to take care of providing the tcp socket.
Yes, I think things like this will happen a lot. Many things will probably be standardized/shared :thumbs_up:
George said:
I wonder if it would make sense to externalize
Tcp(or something similar that represents a bi-directional communication channel) into a separate package similar to howroc-lang/httpdoes so forRequestandResponse. Then libraries could be written around that and the platform / app would only have to take care of providing the tcp socket.
I am not sure how frowned upon @-ing is here but I'd be curious what @Richard Feldman thoughts on this are (or any of the other maintainers).
Because such an approach (as mentioned for http and Request/Response) works best if there is a blessed package that everyone uses
yeah, I'm actually less sure about Request and Response after doing a big investigation of those :smile:
to be clear, we are in uncharted territory and I think there are things that make sense to try, but I don't think we can know what the outcome of the experiments are until we have tried them
and definitely the thing that makes sense to try first is the most-flexible and least-invasive one, which is basically "we have enough primitives to offer platform-agnostic packages that have to be provided with the effects they need, so let's try that and see how it goes"
as an example of why I'm less sure about Request and Response belonging in builtins: what if I had this for an error-reporting package for Sentry?
Sentry.init : {
send! : {
url : Str,
verb : [GET, POST, PUT],
body : Str,
headers : List((Str, Str)),
} => Try({}, {
status : U16,
body : Str
}),
} -> Sentry
so the way I would use this is something like this:
sentry = Sentry.init({
send!: |{ url, verb, body, headers}| {
if url.starts_with("https://api.sentry.com/report/") {
pf.Http.request!({
url,
verb,
body,
headers: headers.append("X-API-Key", my_api_key)
}).map(|resp| {
{ status: resp.status(), body: resp.body() }
})
} else {
# Security alert! Suspicious package!
}
}
})
so, this is a bit of boilerplate, but it's literally one function call to set up the entire package being longer (no matter what, you always have to do something like this to set up the API key). Doesn't feel excessive to me.
and the advantages are nice I think:
Request or Response types needed - you just specify right in that setup function how to translate between the body and URL Str values and the status code U16 and that's about iturl.starts_with check to make sure the library is not doing any funny business, and also the headers.append("X-API-Key", my_api_key) so the package literally never even sees your API key, so there's nothing it could compromise (more relevant if this were like an AWS access package instead of Sentry)send! instead of a real one from pf)is this certainly the long term optimal design? I don't know!
but I think it's the right thing to try first, because it already works today, and it seems like the tradeoffs are already nice in an example like this - so I'd be curious to see how nice we can get things with other use cases like TCP
This looks great. :+1: Some questions/comments:
send! method. It would be nice to be able to just point to a standard type def for common platform functions, no? Not just for convenience, but also for versioning and for standardization across platforms.I guess secrets could work like this:
get_secret : Str -> Secret function. This is a fully opaque type: the Roc code (app & packages) can't do much with it other than pass it around.send! function converts the Secrets back to strings.Last updated: Sep 03 2026 at 15:16 UTC