[MYSTERY SOLVED] REST API: How to simply GET a record usinf frappe-ui?

See the sequence around https://youtu.be/nnnfMc7h5wE?t=4223
We have the id (route.params.id), the comment is entered to load the record, then … let’s do something else.

Additionally, since you simply read the id from the URL, this mean that anyone can brute force with forging multiple URLs to read more records.

Well, unless there is some other setup needed, like authorization key/secret.

The routing etc. might contain mechanisms against abuse, at least the framework should protect somehow against it.

Maybe @buildwithhussain can help with the missing bit?

It’s been a while since I used FrappeUI, but doesn’t createResource just poll any arbitrary endpoint and parse the JSON that gets returned? Are you getting errors when you call something like this?

const todo = createResource({
   url: '/api/resource/ToDo/0489l150rq'
})

OKAY, I fixed it.
I misunderstood multiple things:

  • when you want to get_doc, you get_list instead. Yeah, a list of one record. In this realm get_doc doesn’t work, period. The reply about whitelist is misleading since it’s not available at all from there.
  • despite being clearly a GET request called get_list, it’s a POST, so forcing method: 'GET' (which is allowed but not working) didn’t help.
  • frappe.get_list doesn’t exist in this vue realm, but frappe.client.get_list does. I thought client was an example of the called app/module, it’s actually another endpoint made for the JS client, AFAIU.
  • filters:'{name: route.params.id}' as in the doc, actually breaks frappe (Internal Server Error)
  • you actually don’t even need to read the id from the URL (but it will do it internally), so remove import { useRoute } from 'vue-router' and route = useRoute() altogether.
  • also forget about onMounted as presentend in the tutorial. Not necessary to read data.
  • if you get error subtree is null, this only mean you need to restart bench! (Insane)

I had to doubt the tutorial and the frappe-ui doc (filters) to make it work! :laughing:
Very weird.

Here’s the final version:

import { createResource } from 'frappe-ui'
    let todo = createResource({
        url: 'frappe.client.get_list',
        params:{
            doctype: 'ToDo',
            fields: ['description', 'status', 'date', 'name'],
        }
    })
    todo.fetch()

And simply <pre>{{ JSON.stringify(todo.data[0],null,2) }}</pre> to see it

    {
      "description": "test1",
      "status": "Open",
      "date": "2025-01-14",
      "name": "0489l150rq"
    }

url: ‘/api/resource/ToDo/0489l150rq’

Thanks @peterg but it throws Internal server error (which error exactly is a mystery, no error.log on dev?).

1 Like

Hi @frappefr:

Why not using createDocumentResource ?
It will bring the whole document.

const todo=createDocumentResource({
  doctype: "ToDo",
  name: 0489l150rq,
  auto: true,
});

createListResource is avaliable too for lists …

Anyway, I think more data components would be coming :crazy_face:
Hope this helps.

2 Likes

Thank you @avc
AFAIK, with createDocumentResource you can’t define which fields you want (fields: ['description', 'status', 'date', 'name']) it, so you get all the fields:

{
  "doctype": "ToDo",
  "name": "0489l150rq",
  "doc": {
    "name": "0489l150rq",
    "owner": "Administrator",
    "creation": "2025-01-14 10:39:57.545285",
    "modified": "2025-01-14 10:39:57.545285",
    "modified_by": "Administrator",
    "docstatus": 0,
    "idx": 0,
    "status": "Open",
    "priority": "Medium",
    "color": null,
    "date": "2025-01-14",
    "allocated_to": null,
    "description": "test1",
    "reference_type": null,
    "reference_name": null,
    "role": null,
    "assigned_by": null,
    "assigned_by_full_name": null,
    "sender": null,
    "assignment_rule": null,
    "doctype": "ToDo"
  },
  "originalDoc": {
    "name": "0489l150rq",
    "owner": "Administrator",
    "creation": "2025-01-14 10:39:57.545285",
    "modified": "2025-01-14 10:39:57.545285",
    "modified_by": "Administrator",
    "docstatus": 0,
    "idx": 0,
    "status": "Open",
    "priority": "Medium",
    "color": null,
    "date": "2025-01-14",
    "allocated_to": null,
    "description": "test1",
    "reference_type": null,
    "reference_name": null,
    "role": null,
    "assigned_by": null,
    "assigned_by_full_name": null,
    "sender": null,
    "assignment_rule": null,
    "doctype": "ToDo"
  },
  "isDirty": false,
  "auto": true,
  "get": {
    "url": "frappe.client.get",
    "data": {
      "name": "0489l150rq",
      "owner": "Administrator",
      "creation": "2025-01-14 10:39:57.545285",
      "modified": "2025-01-14 10:39:57.545285",
      "modified_by": "Administrator",
      "docstatus": 0,
      "idx": 0,
      "status": "Open",
      "priority": "Medium",
      "color": null,
      "date": "2025-01-14",
      "allocated_to": null,
      "description": "test1",
      "reference_type": null,
      "reference_name": null,
      "role": null,
      "assigned_by": null,
      "assigned_by_full_name": null,
      "sender": null,
      "assignment_rule": null,
      "doctype": "ToDo"
    },
    "previousData": null,
    "loading": false,
    "fetched": true,
    "error": null,
    "promise": {},
    "params": {
      "doctype": "ToDo",
      "name": "0489l150rq"
    }
  },
  "setValue": {
    "url": "frappe.client.set_value",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  },
  "setValueDebounced": {
    "url": "frappe.client.set_value",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  },
  "save": {
    "url": "frappe.client.set_value",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  },
  "delete": {
    "url": "frappe.client.delete",
    "data": null,
    "previousData": null,
    "loading": false,
    "fetched": false,
    "error": null,
    "promise": null,
    "params": null
  }
}

So far, I don’t get what’s the use case of it.

createListResource (list of one) works and also use get_list. You need to import { useRoute } from 'vue-router';

I really wonder why 3 different and so identical functions, 1 doesn’t implement fields, another need url and params, and this last one seems to work for all cases.
Hmmm, createResource might be just in case to want to fetch external public data, which is super niche.
createDocumentResource for:
- edit (setValue.submit({ status: ‘Closed’ }))
- whitelistedMethods

We use that for get the document and update it from client side easily.

Try createListResource instead …

let todos = createListResource({
  doctype: 'ToDo',
  fields: ['name', 'description', 'status'],
  orderBy: 'creation desc',
  start: 0,
  pageLength: 5,
})
1 Like

Umm … like get_list vs get_doc

Many times you need just a list, other you need the whole document (child tables data included …) . I see both needed :slight_smile:

good point.

Can you share how you do it using frappe-ui?

Here I am using frappe-ui :joy:

Jokes aside, we are building some little apps for different purposes (field force, HR checkin …). Best way to learn is diving the code of Frappe apps … LMS, HelpDesk, Gameplan, Drive …

3 Likes

You’re lucky! I usually blow every mine in the frappe-ui minefield.

2 Likes

FrappeUI is growing and pretty undocumented yet … but is fantastic and easy to learn.

This recent video from @buildwithhussain is really good too.

2 Likes

There are different levels of abstraction and also a data hierarchy.

If you ask for a list, you get just the “names” of the documents: the unique doc references for the “rows” in a doctype table. So you can get a big list with little data. Then you can drill down to get documents.
With lots of data you also will need to include paging when advancing through lots of data.
You can pick fields, which can bring you between mere “names” (really IDs) and full documents including subtables. These documents can get big.
So with the need to navigate the tradeoffs between “many but small” and “just one but very big”, there are different possibilities to build stuff.

Also, if you get a doc, set some fields to new values and and then just “insert” or “put” or however the API calls its methods, then it also gets interesting because you can manage big docs (DT instances with many fields) with just some lines of code, and the complexity of big, complicated custom DTs is just kind of hidden behind some beautifully crafted lines of the nice framework.

So, all this software crafting has many interesting facets.

I may be wrong, but for the moment, I disagree. The three of them provide basically the same service when tailored with fields (when possible).

If they are used without fields, I don’t want a function (createListResource) to get only a cryptic list of names (0489l150rq, … because doctype don’t have to be tailored (Title field) based on the limitations of the API right?), then click randomly on one record to get (createDocumentResource) ALL the fields and beyond, even data that is not related to the doctype:

  • all the meta (allocated_to, … 17 lines of key-value pairs, many null or data that should not be visible by clients or guests)
  • the doctype history (originalDoc, 21 fields with a lot of nulls),
  • repetition of my request (28 lines)
  • “hidden doc” about functions (40 lines), useless since you can insert, update, delete without calling createDocumentResource (todos.insert.submit)

All this for EACH record (you can’t “pick fields” in createDocumentResource).
(count them in my post above [MYSTERY SOLVED] REST API: How to simply GET a record usinf frappe-ui? - #17 by frappefr)

That’s more than 100 key-value pairs not related to the doctype itself (3-6 useful fields in a todo).
Even if it was only for trusted clients, it’s already slow, I don’t want customers to GET all this (including name of staff who modified the record), then filter out everything on the client.
Hope this make more sense.

I’d still say it depends on the use case.

You could have sales orders with hundreds of line items, even with photos.
Lag could be an issue.
You could build an archive app with extreme requirements.
A book app with thousands of scanned pages.
Or some other kind of data silo with many many detailed but small info bits (web logs, car flows, weather logs every minute over 30 years, you name it).
Requirements and limitations of can vary wildly.

Whatever, no need to argue, I wish you fun and success building apps with your framework and UI of choice.

Strange, it works fine for me. If you’re in a development environment, errors would go straight to the console via stdout. Are you not seeing them there?

That said, I agree fully with @avc that createListResource and createDocumentResource are usually better interfaces for frappe data. In FrappeUI, createResource is the generic http interface, and it works for both frappe and non-frappe data. It’s also used for calling system-level methods that aren’t tied to a specific doctype.

Others have explained why a List/Doc distinction exists, but one thing I’d add:

Just be careful here, because using createListResource to filter fields isn’t a protection. If you have data that you don’t want guests or customers to see, there’s no javascript solution to that problem. Your permissions configuration on the backend is the only thing that matters here.

Soon this will be usable …

1 Like

Maybe I don’t get what you meant, I didn’t mean to argue, I apologize.

1 Like

That’s what I get (server and browser console):

Maybe we don’t have the same versions?

erpnext 15.x.x-develop
frappe 15.x.x-develop
"frappe-ui": "^0.1.98"

The only error.log I have is worker.error.log:

I totally agree.
This raises another question: How to prevent certain (meta) fields to be read by certain users?
I don’t see this “permission by field” in the doctype. “Permissions” tab is for all records, fields don’t have this granularity.
Could you please show me an example @peterg?

Don’t worry; I said this to myself as well.
Our perspectives are somewhat different, I was looking from a point closer to the technical side, the transmissions and such, and you know more about the frappe UI, which I found (and find) interesting and inspiring: I’ll look into this more, but can’t do that ATM, but will probably need it at a later time.

1 Like