Serve anything, anywhere on your domain, with @anvil.server.route()

Yep, it’s here. Respond to any path within your app’s domain with @anvil.server.route:

@anvil.server.route("/hello")
def hello(**p):
  return {"hello":"world"}
$ curl https://my-interesting-app.anvil.app/hello
{"hello":"world"}

:tada:

But wait - there’s more!

What if you don’t just want to serve a machine-readable response? What if you want to serve a human-accessible UI on a particular URL instead?

Just return a FormResponse object:

@anvil.server.route("/my-page")
def my_page(**p):
    return anvil.server.FormResponse("Form2")

You can even pass arguments into your forms - and yes, that includes data tables, portable classes and Media! For example:

@anvil.server.route("/users/:email")
def serve_user_page(email, **p):
  user = app_tables.users.get(email=email)
  if user:
    return anvil.server.FormResponse('UserPage', user=user)
  else:
    return anvil.server.HttpResponse(404, "No such user")

Is that the sound of loading an entire page at once and rendering it in one go? Yes, I think it is.

Docs

Check out the new additions to our HTTP API documentation:

Watch this space

I would describe this particular feature as a “building block” release. We’re aiming for a world where multi-page, multi-URL apps with Anvil are easy for anyone to build. This API on its own doesn’t take us all the way there - but “perfect” is the enemy of “done”, so we’re putting it out there for you to use right away!

30 Likes

Ooo, this looks interesting (assuming I have understood correctly).

This is amazingggggggg

I can finally build my landing pages in Anvil and not worry about SEO! I think

1 Like

How would I square this with user navigation - i.e. routing to a form using client code?

2 Likes

I think that routing to a form using client code belongs to Watch This Space above.

In the meantime, there’s Anvil Extras Hash Routing.

I was thinking to play with history.pushState and history.replaceState (which is what Anvil Extras does), but right now I don’t have the time to experiment.

I think the right way would be to make a call to history.replaceState every time a form that has its own route is shown.

For example, each form with a route could check its arguments, then build the URL and use history.replaceState to update the current URL without reloading the app in its __init__.

At this point, you can start the app from any route, and you can navigate to any form from inside the app, and the current URL will always reflect what you are seeing. Whatever form you are looking at, whether you are there because you navigated to it from inside the app or from Google, the URL will always match the content.

To quote The Good Place: Holly forking shirt.

Top work Anvil Team!

2 Likes

I was already happy enough with @anvil.server.route but looks like there was more! anvil.server.FormResponse was a totally unexpected addition. This really helps!

2 Likes

Hi @yahiakalabs, here is a mini example.

You can go to the root URL and start the app from the home page, you can put the page in the URL, and you can put the page and an option in the URL.

As you change forms or options inside a form, the URL and the tab title keep themselves updated.

There is no management of the browser history buttons (yet).

(I don’t like the way I worked around circular imports, but it’s a quick proof of concept, and for now it’s good enough.)

EDIT
I just fixed a bunch of bugs (who knew such a little app could have so many bugs!)

1 Like

Thanks for the example! That is indeed really cool - I wonder if it’s that easy to replace the hash routing. Looking forward to see how this develops.

Hash routing has grown overtime to include more and more features. I only use a few of them.

The two parts that I miss, comparing my little example app to what I usually do with hash routing, are:

  • Management of history back and forward buttons.
  • Caching of forms. The hash routing can reload the form or show the form that was cached when navigating away from it.

Perhaps it’s as easy as replacing replaceState with pushState and giving a little more love to the history object, but there may be surprises because, while the hash routing only replaces the client side part of the URL, the new routing replaces the server side. I can’t think of any reason why it wouldn’t work, but I haven’t tried yet.

I’m not in a hurry to try, simply because I am not working on new apps right now, and because I don’t want to spend time on something that will (very hopefully) soon be obsolete:

1 Like

With the new server routing, its actually pretty easy to get basic client navigation with the window.location object:

import anvil.server
from anvil.js import window


def change_path(path_changes: str, changes_path: bool):
  if changes_path:
    new_path = f"{anvil.server.get_app_origin()}/{path_changes}"
  else:
    new_path = f"{window.location.href}/{path_changes}"
  window.location.assign(new_path)

I think that assigning an URL to the window.location redirects to the new URL, which will cause the app to reload from scratch. This at best has a bad impact on performance, and at worst breaks functionalities…

A better way is what I tried in my previous post, where you can start the app from any URL, so the app goes directly to the specified page, but you load a new form, rather than loading the whole app, when the app is already running and the user navigates to another form of the same app.

In the Anvil IDE, every time you click on a code editor, the URL changes, but the IDE does not refresh. The same in my little test app: when you change the color of the apple, the URL changes. In both cases, at any time during the session, you can bookmark to the current form and attributes.

1 Like

This is a big deal, nice job team

Very interesting :slight_smile:

A question regarding FormResponse. Lets say i have a Form with a Navbar at the top and the main content below. Does all forms need to have a navbar (static navbar)? If possible i would just like the main content to be rerendered. :slight_smile:

You might want to explore layouts for this. If your main content is using a layout that has a navbar then it should just work.

2 Likes

Thanks, will check it out :slight_smile:

You can use layouts as suggested by @stucork, but you can also use the old way, with a main form with nav bar that loads a sub-form.

For example you could do something like this:

@`anvil.server.route("/invoice/:number")`
return anvil.server.FormResponse('Main', sub_form='Invoices', number=number)

And then Main would take care of loading the correct sub-form with the correct arguments.

4 Likes

Sample Main Form + Sub Form routing as suggested by @stefano.menci

1 Like

Hi @meredydd
This is great. I have one question. Let’s say Form2 or UserPage is located inside a module, not in direct Client side, i.e. Client-side/Forms/Form2.
In this case, I get error

ModuleNotFoundError: No module named 'Form2'

I tried Forms/Form2, but I get another error.
return anvil.server.FormResponse('Forms/Form2')

AttributeError: "Forms/Form2" module does not contain a class called "Forms/Form2"

How can I modify your code to serve in this case?