Closed houstonhaynes closed 1 month ago
Hi @houstonhaynes,
I fully understand your confusion, but this is coming from the specifics of single page apps (SPA) vs classic API endpoint. Let me adreess is one by one:
First off - there's no index.html
There is. Located in Client
project. If you run dr publish
command, you will see the final output of the build, where index.html
is correctly located. And this index.html
is used for every response for SPA, because... that's how SPA works. If you go to let's say /about
page, what actually happens is that:
/about
routeindex.html
insteadThe same goes when you do the routing in SPA without full refresh - JavaScript just changes the URL in the browser + renders a different part of the SPA. That's where SINGLE-PAGE naming comes from. Confusing, yeah, but all went through the same horror.
Secondly - and this is the interesting bit - when I go to the "GetMessage" endpoint - I also get the index.html page in return.
Again, this is something that may slip through your attention when reading Remoting docs. For any server call where there may be some parameter coming to the server (like in /GetMessage
), it uses POST (!!! important)
This POST normally returns JSON as you would expect.
I don't really recommend to getting rid of the Shared
project, because it's actually the only reason I would go for SPA, Fable & Remoting. Without the shared part, the benefits of this templates are close to zero. 😅
I hope this helped and wish you happy F# coding! Glad to have you in community! 💪
Btw, here you can see the production-ready app with routing, different parts, shared part, etc...
Thanks for taking the time to respond. I think we have tripped up on a slight miscommunication, which is caused a pretty significant Divergence and what I'm talking about and what your response includes.
I was strictly talking about server and how server was set up. There is a route that goes to public/index.html but there is no HTML generated in the template.
To be clear, I'm not getting rid of Shared at all - but I'm not importing it into server to use the service definition that's included there. I'm essentially building my own routing manually because the routing as included in the shared API does not seem to work.
routing as included in the shared API does not seem to work
This is where I don't understand the issue. If you take what is in the template and copy-paste, you can have several APIs including routing for the frontend part. Maybe you are trying to use the Remoting library for REST API server consumed NOT from the SPA? Then of course you should create your own - this Shared is mainly for communication between frontend and backend.
There is a route that goes to public/index.html but there is no HTML generated in the template.
Yes, in development mode, the index.html is returned by Vite.js dev server. In production mode dr publish
you will find it in the right place - public/index.html - and you can directly deploy like that to Docker, Azure, etc...
Example
API definition in Shared: https://github.com/Dzoukr/FuncasterStudio/blob/master/src/FuncasterStudio.Shared/Episodes/API.fs Server implementation: https://github.com/Dzoukr/FuncasterStudio/blob/master/src/FuncasterStudio.Server/Episodes/API.fs Server registration: https://github.com/Dzoukr/FuncasterStudio/blob/master/src/FuncasterStudio.Server/WebApp.fs Client usage:
OK - this is a conceptual problem on my part. What I'm seeing here is that since there's not any RESTful endpoints to separately check the validity of the server deploying that essentially hard couples the front end and back end together. I'm not sure I'm a fan of that idea. But I'll go through the example see if I can get my head wrapped around the assumptions.
It's not a problem on your part (you are too hard on yourself). It's just a different approach. You can easily set FAKE scripts to deploy FE or BE only, but when using Shared
project, these things are tangled together anyway. For having RESTful endpoints, just see how Giraffe works and on the frontend side, you can use something like Thoth.Json to have it truly separated. But some SPA specifics like routing still remains. For choosing the correct communication, see the table below:
More info on that is on official SAFE template: https://safe-stack.github.io/docs/features/feature-clientserver/
Maybe last remark: Fable.Remoting is a great library when "I don't care about HOW the communication looks like, Ï just need a strongly-typed safe data exchange between MY frontend and MY server." Once there are more potential consumers (other systems using your REST API), there Fable.Remoting is no-go (or use it only for you on some /internal-api/
path).
I hear you - and understand the natural tension there - and I've felt it. There's the desire to avoid/circumvent the standard pratfalls of "JSON all the thingz" in the HTTP/RESTful world. I've even dealt with the limits/burdens of that in the protobuf "sphere". Even there I have my own misgivings about whether the attempt at data guarantees 'over the wire' is a fool's errand (as it would create a level of coupling that would be "golden handcuffs" and a recipe for building up technical debt faster than it can be refactored in a changing business landscape). At that point it ceases to be a "stack" and is just another monolith, which I want to avoid.
I didn't even know about Elmish.Bridge until your mention of it above, so there's definitely a need for me to expand my survey of the landscape.
Such as it is - I'm still a bit mystified (and likely a blind spot on FAKE) by the prospect of deploying the Client to Cloudflare Pages and the Server to DigitalOcean and have them "talk to each other" without wiring things up "old school". I don't mind staying canonical with Fable.Remoting for this particular application because it's really just a sales lead pipeline management tool. BUT and there's always that but - the actual showcase app we're building is more 'hairy' and we do have ambitions to grant some degrees of freedom to where an Avalonia client (mobile/tablet/desktop, you get the idea) uses the same back end - hopefully with the same type safety assurances offered in a "canonical" SAFE stack.
That's probably more than I should try to address in the first go with this stack, but at least I wanted to paint the bigger picture in order to provide the broader goal(s).
Thanks again!
BTW - I've never seen Server deploy in anything other than "Production" mode (which limits logging output) even when deploying to localhost:5000 and a public/index.html on Server was never generated when deploying using 'dotnet run' as instructed in the guidance.
Got everything "cleaned up" and working with the standard Fable.Remoting doing the routing and it's all good. Thanks again for cribbing me through. I just have trust issues and ketp trying to hand-roll everything when it's really not needed in this case. I'll save that for where it's warranted. ⭐
So I was pretty excited about this template - and still am - but have hit a few head-scratching issues that caused me to re-create a new project with a "vanilla" template and realizing there are some gaps in the assumptions in the boilerplate code.
First off - there's no index.html. So if you try to run the server from a cold start and go to the home path as the logs instruct you get a big nasty .NET error. Once I added a placeholder index.html that bit was taken care of.
Secondly - and this is the interesting bit - when I go to the "GetMessage" endpoint - I also get the index.html page in return.
Even more interesting - if I put in a wrong endpoint I also get back the index.html page.
I don't know that much about Fable.Remoting so I just hand-jammed my way through to getting things to behave as I expect using this Program.fs
This gives me what I would expect from the boilerplate - a static index page for the root, a "Hello, World!" response from
api/service/GetMessage
and "Not Found" for everything else. I may eventually take a turn at Fable.Remoting to understand it better and perhaps tweak what's going on in this template.But for now I just need a pair of forms and a bit of interaction to work so I'm going to keep hand-jamming in Program.fs for this particular project. It's a shame that I had to "throw out" Shared.API because I wanted to give it a try under these limited/controlled circumstances. So I expect to return to this at some point to deepen my understanding of the mechanism "a few layers down". In the meantime if there's interest in checking this out by someone who knows this "stack" better, I'd be eternally grateful. (even if it's just a documentation update to notify newbies to this template what to expect with server as-configured) Thanks!