> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Conozca las diferencias entre usar el almacén de datos de Auth0 y una base de datos externa para almacenar datos de usuario.

# Almacenamiento de datos de usuario

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****ENMASCARADO*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

Auth0 almacena la información de los usuarios de su inquilino en una base de datos alojada en la nube, o bien puede optar por almacenar los datos de usuario en su propia base de datos externa personalizada.

Para almacenar datos de usuario además de la información básica que Auth0 utiliza para la autenticación, puede usar el almacén de datos de Auth0 o una base de datos personalizada. Sin embargo, si utiliza los datos adicionales con fines de autenticación, le recomendamos usar el almacén de datos de Auth0, ya que esto le permite administrar sus datos de usuario desde el [Dashboard de administración de Auth0](https://manage.auth0.com/#).

<div id="external-database-vs-auth0-data-store">
  ## Base de datos externa frente al almacén de datos de Auth0
</div>

El almacén de datos de Auth0 está diseñado para datos de autenticación. Almacenar cualquier cosa más allá de la información predeterminada del usuario solo debería hacerse en casos limitados. A continuación se explica por qué:

* **Escalabilidad**: El almacén de datos de Auth0 tiene limitaciones de escalabilidad, y los datos de su aplicación pueden superar los límites adecuados. Al usar una base de datos externa, mantiene simple su almacén de datos de Auth0, mientras que la base de datos externa, más eficiente, contiene los datos adicionales;
* **Rendimiento**: Es probable que se acceda a sus datos de autenticación con menos frecuencia que a sus otros datos. El almacén de datos de Auth0 no está optimizado para un uso de alta frecuencia, por lo que debería almacenar en otro lugar los datos que deban recuperarse más a menudo;
* **Flexibilidad**: Debido a que el almacén de datos de Auth0 se creó para admitir únicamente perfiles de usuario y sus metadatos asociados, sus posibilidades de actuar sobre la base de datos son limitadas. Al usar bases de datos independientes para sus otros datos, puede gestionarlos según corresponda y realizar llamadas directas sin usar la <Tooltip tip="Management API: Un producto que permite a los clientes realizar tareas administrativas." cta="Ver glosario" href="/es/docs/glossary?term=Management+API">Management API</Tooltip> de Auth0.

Al externalizar la autenticación de usuarios, por lo general no es necesario mantener su propia tabla de usuarios/contraseñas. Aun así, es posible que quiera asociar los datos de la aplicación con usuarios autenticados.

* Por ejemplo, podría tener una tabla Users que enumere cada usuario autenticado por Auth0. Cada vez que un usuario inicie sesión, podría buscar a ese usuario en la tabla. Si el usuario no existe, crearía un nuevo registro. Si sí existe, actualizaría todos los campos, manteniendo así una copia local de todos los datos del usuario.
* Como alternativa, podría almacenar el identificador del usuario en cada tabla/colección que tenga datos asociados al usuario. Esta es una implementación más sencilla, adecuada para aplicaciones más pequeñas.

<div id="user-data-storage-example-scenario">
  ## Escenario de ejemplo de almacenamiento de datos de usuario
</div>

Auth0 proporciona una aplicación de ejemplo —una aplicación móvil de música— que refleja la experiencia completa del usuario al usar Auth0 con una base de datos externa personalizada. La aplicación de ejemplo es una app para iOS creada con el [proyecto semilla de Auth0 para iOS](/es/docs/quickstart/native/ios-swift). El backend usa la [API de Node.js](/es/docs/quickstart/backend/nodejs).

Para ver una visualización de la estructura general de la aplicación, consulte el [escenario de arquitectura móvil + API](/es/docs/get-started/architecture-scenarios/mobile-api).

<div id="metadata">
  ### Metadatos
</div>

<div id="app-metadata">
  #### Metadatos de la aplicación
</div>

Los siguientes datos de nuestra aplicación móvil de música son adecuados para almacenarse en `app_metadata`:

* Plan de suscripción del usuario
* Derecho del usuario (o su falta de autorización) para editar listas de reproducción destacadas

Estos dos datos deben almacenarse en `app_metadata` en lugar de `user_metadata` porque el usuario no debería poder modificarlos directamente.

<div id="user-metadata">
  #### Metadatos de usuario
</div>

Los siguientes datos de nuestra aplicación móvil de música son adecuados para almacenarse en `user_metadata`:

* Preferencias de la aplicación
* Opciones elegidas por el usuario para modificar su experiencia en la aplicación al iniciar sesión.

Ten en cuenta que, a diferencia de los datos de `app_metadata`, el usuario puede cambiar fácilmente los almacenados en `user_metadata`.

Podemos permitir que el usuario cambie su `displayName`, que es el nombre que ve al iniciar sesión y que se muestra a otros usuarios de la aplicación.

Para mostrar el identificador elegido por el usuario cada vez que inicia sesión, usamos una Rule para obtener el valor de `user.user_metadata`.

```javascript lines theme={null}
function(user, context, callback){
  user.user_metadata = user.user_metadata || {};
  user.user_metadata.displayName = user.user_metadata.displayName || "user";

  auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
    .then(function(){
      callback(null, user, context);
    })
    .catch(function(err){
      callback(err);
    });
}
```

A continuación se muestra la pantalla que usaría el usuario para cambiar su `displayName`:

<Frame>
  <img src="https://mintcdn.com/translations/Dcx0M11uuptU53TX/docs/images/cdy7uua7fh8z/33YxGQKYztHY3OM6mxJ4jN/0a06103e6b6455c2e1962a1f738b663a/4-settings.png?fit=max&auto=format&n=Dcx0M11uuptU53TX&q=85&s=55de886498d279cf6d10c1378391a7c0" alt="Pantalla de configuración de la app de iOS con la opción de actualizar el nombre para mostrar." width="300" height="534" data-path="docs/images/cdy7uua7fh8z/33YxGQKYztHY3OM6mxJ4jN/0a06103e6b6455c2e1962a1f738b663a/4-settings.png" />
</Frame>

Para guardar los cambios en la base de datos, la aplicación realiza una llamada al endpoint [Get a User](https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id) de la Management API para identificar al usuario adecuado:

<AuthCodeGroup>
  ```bash cURL lines theme={null}
  curl --request GET \
    --url https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id \
    --header 'authorization: Bearer {yourIdToken}'
  ```

  ```csharp C# lines theme={null}
  var client = new RestClient("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id");
  var request = new RestRequest(Method.GET);
  request.AddHeader("authorization", "Bearer {yourIdToken}");
  IRestResponse response = client.Execute(request);
  ```

  ```go Go lines theme={null}
  package main

  import (
  	"fmt"
  	"net/http"
  	"io/ioutil"
  )

  func main() {

  	url := "https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id"

  	req, _ := http.NewRequest("GET", url, nil)

  	req.Header.Add("authorization", "Bearer {yourIdToken}")

  	res, _ := http.DefaultClient.Do(req)

  	defer res.Body.Close()
  	body, _ := ioutil.ReadAll(res.Body)

  	fmt.Println(res)
  	fmt.Println(string(body))

  }
  ```

  ```java Java lines theme={null}
  HttpResponse<String> response = Unirest.get("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")
    .header("authorization", "Bearer {yourIdToken}")
    .asString();
  ```

  ```javascript Node.JS lines theme={null}
  var axios = require("axios").default;

  var options = {
    method: 'GET',
    url: 'https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id',
    headers: {authorization: 'Bearer {yourIdToken}'}
  };

  axios.request(options).then(function (response) {
    console.log(response.data);
  }).catch(function (error) {
    console.error(error);
  });
  ```

  ```php PHP lines theme={null}
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer {yourIdToken}"
    ],
  ]);

  $response = curl_exec($curl);
  $err = curl_error($curl);

  curl_close($curl);

  if ($err) {
    echo "cURL Error #:" . $err;
  } else {
    echo $response;
  }
  ```

  ```python Python lines theme={null}
  import http.client

  conn = http.client.HTTPSConnection("")

  headers = { 'authorization': "Bearer {yourIdToken}" }

  conn.request("GET", "%7ByourAccount%7D.auth0.com/api/v2/users/user_id", headers=headers)

  res = conn.getresponse()
  data = res.read()

  print(data.decode("utf-8"))
  ```

  ```ruby Ruby lines theme={null}
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://%7ByourAccount%7D.auth0.com/api/v2/users/user_id")

  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = Net::HTTP::Get.new(url)
  request["authorization"] = 'Bearer {yourIdToken}'

  response = http.request(request)
  puts response.read_body
  ```
</AuthCodeGroup>

A continuación, se hace una llamada al endpoint [actualizar un usuario](https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id) para actualizar el campo `user_metadata`:

<AuthCodeGroup>
  ```bash cURL theme={null}
  curl --request PATCH \
    --url 'https://{yourDomain}/api/v2/users/user_id' \
    --header 'authorization: Bearer {yourAccessToken}' \
    --header 'content-type: application/json' \
    --data '{"user_metadata": {"displayName": "J-vald3z"}'
  ```

  ```csharp C# theme={null}
  var client = new RestClient("https://{yourDomain}/api/v2/users/user_id");
  var request = new RestRequest(Method.PATCH);
  request.AddHeader("authorization", "Bearer {yourAccessToken}");
  request.AddHeader("content-type", "application/json");
  request.AddParameter("application/json", "{"user_metadata": {"displayName": "J-vald3z"}", ParameterType.RequestBody);
  IRestResponse response = client.Execute(request);
  ```

  ```go Go theme={null}
  package main

  import (
  	"fmt"
  	"strings"
  	"net/http"
  	"io/ioutil"
  )

  func main() {

  	url := "https://{yourDomain}/api/v2/users/user_id"

  	payload := strings.NewReader("{"user_metadata": {"displayName": "J-vald3z"}")

  	req, _ := http.NewRequest("PATCH", url, payload)

  	req.Header.Add("authorization", "Bearer {yourAccessToken}")
  	req.Header.Add("content-type", "application/json")

  	res, _ := http.DefaultClient.Do(req)

  	defer res.Body.Close()
  	body, _ := ioutil.ReadAll(res.Body)

  	fmt.Println(res)
  	fmt.Println(string(body))

  }
  ```

  ```java Java theme={null}
  HttpResponse<String> response = Unirest.patch("https://{yourDomain}/api/v2/users/user_id")
    .header("authorization", "Bearer {yourAccessToken}")
    .header("content-type", "application/json")
    .body("{"user_metadata": {"displayName": "J-vald3z"}")
    .asString();
  ```

  ```javascript Node.JS theme={null}
  var axios = require("axios").default;

  var options = {
    method: 'PATCH',
    url: 'https://{yourDomain}/api/v2/users/user_id',
    headers: {authorization: 'Bearer {yourAccessToken}', 'content-type': 'application/json'},
    data: '{"user_metadata": {"displayName": "J-vald3z"}'
  };

  axios.request(options).then(function (response) {
    console.log(response.data);
  }).catch(function (error) {
    console.error(error);
  });
  ```

  ```php PHP theme={null}
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://{yourDomain}/api/v2/users/user_id",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "PATCH",
    CURLOPT_POSTFIELDS => "{"user_metadata": {"displayName": "J-vald3z"}",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer {yourAccessToken}",
      "content-type: application/json"
    ],
  ]);

  $response = curl_exec($curl);
  $err = curl_error($curl);

  curl_close($curl);

  if ($err) {
    echo "cURL Error #:" . $err;
  } else {
    echo $response;
  }
  ```

  ```python Python theme={null}
  import http.client

  conn = http.client.HTTPSConnection("")

  payload = "{"user_metadata": {"displayName": "J-vald3z"}"

  headers = {
      'authorization': "Bearer {yourAccessToken}",
      'content-type': "application/json"
      }

  conn.request("PATCH", "/{yourDomain}/api/v2/users/user_id", payload, headers)

  res = conn.getresponse()
  data = res.read()

  print(data.decode("utf-8"))
  ```

  ```ruby Ruby theme={null}
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://{yourDomain}/api/v2/users/user_id")

  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = Net::HTTP::Patch.new(url)
  request["authorization"] = 'Bearer {yourAccessToken}'
  request["content-type"] = 'application/json'
  request.body = "{"user_metadata": {"displayName": "J-vald3z"}"

  response = http.request(request)
  puts response.read_body
  ```
</AuthCodeGroup>

Sustituya `{yourAccessToken}` por un [Token de acceso de Management API](https://auth0.com/docs/api/management/v2/concepts/tokens).

<div id="user-data-permission-rules">
  ### Reglas de permisos para datos de usuario
</div>

Use [Rules](/es/docs/customize/rules) para implementar permisos que determinen si un usuario puede editar listas de reproducción destacadas.

<div id="assign-playlist-editor-role">
  #### Asignar el rol Playlist Editor
</div>

La primera Rule envía una solicitud a nuestra API de Node, que luego consulta la base de datos conectada a Heroku para comprobar cuántas reproducciones tiene la lista de reproducción del usuario. Si el número es 100 o superior, asignamos `playlist_editor` como un valor del array `roles` en `app_metadata`.

```javascript lines expandable theme={null}
function (user, context, callback) {

  var request = require('request');

  user.app_metadata = user.app_metadata || {};
  user.app_metadata.roles = user.roles || [];

  var CLIENT_SECRET = configuration.AUTH0_CLIENT_SECRET;
  var CLIENT_ID = configuration.AUTH0_CLIENT_ID;

  var scope = {
    user_id: user.user_id,
    email: user.email,
    name: user.name
  };

  var options = {
    subject: user.user_id,
    expiresInMinutes: 600,
    audience: CLIENT_ID,
    issuer: 'https://example.auth0.com'
  };

  var id_token = jwt.sign(scope, CLIENT_SECRET, options);

  var auth = 'Bearer ' + id_token;

  request.get({
    url: 'https://example.com/playlists/getPlays',
    headers: {
       'Authorization': auth,
      'Content-Type': 'text/html'
    },
    timeout: 15000
  }, function(err, response, body){
    if (err)
      return callback(new Error(err));
    var plays = parseInt(body, 10);

    if (plays >= 100 && user.roles.indexOf('playlist_editor') < 0){
      user.app_metadata.roles.push('playlist_editor');
      auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        .then(function(){
          callback(null, user, context);
        })
        .catch(callback);
    }

    else if (plays < 100 && user.roles.indexOf('playlist_editor') >= 0){
      user.app_metadata.roles = [];
      auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
        .then(function(){
          callback(null, user, context);
        })
        .catch(callback);
    }
    else{
      callback(null, user, context);
    }

  });

}
```

<div id="scope-parameter-specifies-role">
  #### El parámetro scope especifica roles
</div>

La segunda Rule obtiene el campo `app_metadata` y asigna el arreglo `roles` a un campo del objeto del usuario para que se pueda acceder a él sin necesidad de llamar a `app_metadata` desde la aplicación. El parámetro `scope` puede entonces especificar `roles` cuando el usuario inicia sesión, sin incluir todo el contenido de `app_metadata` en el objeto del usuario:

```javascript lines theme={null}
function(user, context, callback) {
   if (user.app_metadata) {
      user.roles = user.app_metadata.roles;
   }
   user.roles = user.roles || [];
   callback(null, user, context);
}
```

Después de implementar estas dos Rules, la aplicación reconoce si el usuario es editor de listas de reproducción o no, y cambia la pantalla de bienvenida en consecuencia. Si `playlist_editor` está en el array `roles` almacenado en `app_metadata` del usuario, se le dará la bienvenida como **EDITOR** después de iniciar sesión:

<Frame>
  <img src="https://mintcdn.com/translations/eVsQcTnbClN-oB7d/docs/images/cdy7uua7fh8z/1WEKC4eWvm8GPK2BlodGFk/3eb36843f22518ace800fc1507bd5816/3-home.png?fit=max&auto=format&n=eVsQcTnbClN-oB7d&q=85&s=5ee92e1fda3ad7e69fb1990f1a60c8f1" alt="Ejemplo de la página de perfil de usuario con el rol de editor." width="300" height="534" data-path="docs/images/cdy7uua7fh8z/1WEKC4eWvm8GPK2BlodGFk/3eb36843f22518ace800fc1507bd5816/3-home.png" />
</Frame>

<div id="associate-a-users-music-with-the-user">
  #### Asociar la música de un usuario a ese usuario
</div>

Necesitamos asociar la música de un usuario a ese usuario, pero esta información no es necesaria para la autenticación. A continuación, se muestra cómo almacenar esta información en una base de datos independiente integrada con el backend de la aplicación.

El identificador único del usuario es `user_id` y es la subpropiedad `sub` del objeto `idTokenPayload` en un `authResult`. Esta es una fila de ejemplo de la tabla `songs` de nuestra base de datos:

| song\_id | songname       | user\_id      |
| -------- | -------------- | ------------- |
| 1        | Number One Hit | google-oauth2 |

El backend de Node.js autentica las solicitudes al URI asociado con la obtención de los datos personales del usuario desde la base de datos mediante la validación de un <Tooltip tip="JSON Web Token (JWT): formato estándar de ID Token (y, a menudo, también de Token de acceso) utilizado para representar claim de forma segura entre dos partes." cta="Ver glosario" href="/es/docs/glossary?term=JSON+Web+Token">JSON Web Token</Tooltip>.

[Obtén más información sobre la autenticación basada en tokens y cómo implementar JWT en tus aplicaciones.](/es/docs/secure/tokens/json-web-tokens)

Aquí tienes el código que implementa la validación de JWT del proyecto semilla de Node.js:

```javascript lines theme={null}
var genres = require('./routes/genres');
var songs = require('./routes/songs');
var playlists = require('./routes/playlists');
var displayName = require('./routes/displayName');

var authenticate = jwt({
  secret: process.env.AUTH0_CLIENT_SECRET,
  audience: process.env.AUTH0_CLIENT_ID
});

app.use('/genres', authenticate, genres);
app.use('/songs', authenticate, songs);
app.use('/playlists', authenticate, playlists);
app.use('/displayName', authenticate, displayName);
```

Podemos añadir funcionalidad para gestionar distintas solicitudes de datos de nuestra aplicación. Por ejemplo, si recibimos una solicitud `GET` a `/secured/getFavGenre`, la API llama a la función `queryGenre()`, que consulta la base de datos y responde con el género favorito del usuario.

```swift lines theme={null}
@IBAction func getGenre(sender: AnyObject) {
        let request = buildAPIRequest("/genres/getFav", type:"GET")
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {[unowned self](data, response, error) in
            let genre = NSString(data: data!, encoding: NSUTF8StringEncoding)
            dispatch_async(dispatch_get_main_queue(), {
                self.favGenre.text = "Favorite Genre:  \(genre!)"
            })
        }
        task.resume()
    }
```

La función `buildAPIRequest()` toma la ruta y el método HTTP de la solicitud como parámetros y crea una solicitud con la URL base de nuestra API de Node.js alojada en Heroku.

En la aplicación, la función `getGenre()` realiza una solicitud a la API y cambia la interfaz para mostrar la respuesta de la solicitud a `/genres/getFav`. El backend recupera los datos necesarios para esta acción mediante la función `queryGenre()` y devuelve los resultados a la aplicación:

```javascript lines theme={null}
function queryGenre(user_id, res){

  db.connect(process.env.DATABASE_URL, function(err, client) {
  if (err) throw err;

  client
    .query('SELECT fav_genre as value FROM user_data WHERE user_id = $1', [user_id], function(err, result) {

      if(err) {
        return console.error('error running query', err);
      }
      res.send(result.rows[0].value);
    });
  });

};
```

<div id="learn-more">
  ## Más información
</div>

* [Perfiles de usuario normalizados](/es/docs/manage-users/user-accounts/user-profiles/normalized-user-profiles)
* [Esquema de perfil de usuario normalizado](/es/docs/manage-users/user-accounts/user-profiles/normalized-user-profile-schema)
* [Perfiles de usuario de ejemplo](/es/docs/manage-users/user-accounts/user-profiles/sample-user-profiles)
