Ejemplos prácticos de FoxServer
Los siguientes ejemplos muestran cómo implementar diferentes tipos de funcionalidades con FoxServer. Puedes usar estos ejemplos como punto de partida para tus propios proyectos.
Ejemplo 1: Hello World
Un controlador simple que responde con un mensaje "Hola Mundo" y demuestra el uso básico de FoxServer.
Controlador (HelloController.prg)
DEFINE CLASS HelloController AS ApiController OLEPUBLIC
* Endpoint simple que devuelve un saludo
PROCEDURE GetHello(req, res) HELP "GET: hello public"
LOCAL loResponse
loResponse = THIS.newObject("status,data,message,timestamp")
loResponse.status = "success"
loResponse.data = "¡Hola, Mundo!"
loResponse.message = "Este es un endpoint de ejemplo"
loResponse.timestamp = DATETIME()
res.Status(200).Json(this.ToJson(loResponse))
ENDPROC
* Endpoint que recibe un parámetro "name" y devuelve un saludo personalizado
PROCEDURE GetHelloName(req, res) HELP "GET: hello/{name} public"
LOCAL loResponse, lcName
lcName = req.GetParam("name", "Invitado")
* Si estás seguro que el parámetro se envía entonces se puede leer así:
* lcName = req.params.name
loResponse = THIS.newObject("status,data,message,timestamp")
loResponse.status = "success"
loResponse.data = "¡Hola, " + lcName + "!"
loResponse.message = "Este es un saludo personalizado"
loResponse.timestamp = DATETIME()
res.Status(200).Json(this.ToJson(loResponse))
ENDPROC
ENDDEFINE
Prueba
Puedes probar estos endpoints con cualquier cliente HTTP o desde el navegador:
# Obtener saludo general
curl http://localhost:8080/api/v1/hello
# Obtener saludo personalizado
curl http://localhost:8080/api/v1/hello/Juan
Nota: Este ejemplo muestra cómo crear un controlador simple y definir endpoints con y sin parámetros.
Ejemplo 2: CRUD Básico
Un ejemplo que implementa operaciones CRUD (Crear, Leer, Actualizar, Eliminar) en una tabla de productos.
Controlador (ProductosController.prg)
DEFINE CLASS ProductosController AS ApiController OLEPUBLIC
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
* Setear configuraciones iniciales
SET DELETED ON
SET EXCLUSIVE OFF
SET CENTURY ON
SET TABLEPROMPT OFF
SET SAFETY OFF
SET TALK OFF
* Abrir tabla de productos
SELECT 0
USE (this.cPath + "data\productos") SHARED
RETURN .T.
ENDFUNC
PROCEDURE AfterEndpoint(req, res)
* Cerrar toda la base de datos
CLOSE DATABASES ALL
ENDPROC
* Obtener todos los productos
PROCEDURE GetProductos(req, res) HELP "GET: productos public"
LOCAL loResponse, lnProductos
* Crear cursor con todos los productos
SELECT * FROM productos INTO CURSOR tmpProductos
lnProductos = _TALLY
SELECT tmpProductos
IF EOF()
loResponse = THIS.newObject("status,data,message")
loResponse.status = "success"
loResponse.data = null
loResponse.message = "No se encontraron productos"
ELSE
* Creamos la respuesta pero con data como un array
loResponse = THIS.newObject(TEXTMERGE("status,data[<>],message"))
LOCAL i
i = 0
* Recorremos el cursor y llenamos el array
SCAN
i = i + 1
SCATTER MEMO NAME loRow
loResponse.data[i] = loRow
ENDSCAN
ENDIF
* Devolver la respuesta
res.Status(200).Json(this.ToJson(loResponse))
ENDPROC
* Obtener un producto específico
PROCEDURE GetProducto(req, res) HELP "GET: productos/{id} public"
LOCAL loResponse, lcId
lcId = req.params.id
* Buscar el producto
SELECT * FROM productos WHERE id = lcId INTO CURSOR tmpProducto
IF _TALLY = 0
loResponse = THIS.newObject("status,data,message")
loResponse.status = "error"
loResponse.data = null
loResponse.message = "Producto no encontrado"
res.Status(404).Json(this.ToJson(loResponse))
ELSE
SELECT tmpProducto
SCATTER MEMO NAME loRow
loResponse = THIS.newObject("status,data,message")
loResponse.status = "success"
loResponse.data = loRow
loResponse.message = "Producto encontrado"
res.Status(200).Json(this.ToJson(loResponse))
ENDIF
ENDPROC
* Crear un nuevo producto
PROCEDURE CreateProducto(req, res) HELP "POST: productos public"
LOCAL loResponse
IF ISNULL(req.json)
loResponse = THIS.newObject("status,error,message")
loResponse.status = "error"
loResponse.error = "Se esperaba un cuerpo JSON"
loResponse.message = "No se proporcionaron datos para crear el producto"
res.Status(400).Json(this.ToJson(loResponse))
RETURN
ENDIF
* Insertar nuevo producto
LOCAL lcGuid
lcGuid = this.NewGuid()
SELECT productos
APPEND BLANK
REPLACE id WITH lcGuid
REPLACE nombre WITH req.json.nombre
REPLACE precio WITH req.json.precio
REPLACE stock WITH req.json.stock
loResponse = THIS.newObject("status,message,producto")
loResponse.status = "success"
loResponse.message = "Producto creado correctamente"
* Devolver el producto creado usando location
res.status(201).location(lcGuid).Json(this.ToJson(loResponse))
ENDPROC
* Actualizar un producto
PROCEDURE UpdateProducto(req, res) HELP "PUT: productos/{id} public"
LOCAL loResponse, lcId
lcId = req.params.id
IF ISNULL(req.json)
loResponse = THIS.newObject("status,error,message")
loResponse.status = "error"
loResponse.error = "Se esperaba un cuerpo JSON"
loResponse.message = "No se proporcionaron datos para actualizar el producto"
res.Status(400).Json(this.ToJson(loResponse))
RETURN
ENDIF
* Buscar y actualizar el producto
SELECT productos
LOCATE FOR id = lcId
IF !FOUND()
loResponse = THIS.newObject("status,error,message")
loResponse.status = "error"
loResponse.error = "Producto no encontrado"
loResponse.message = "No se pudo actualizar el producto porque no existe"
res.Status(404).Json(this.ToJson(loResponse))
RETURN
ENDIF
* Actualizar campos
IF TYPE('req.json.nombre') = 'C'
REPLACE nombre WITH req.json.nombre
ENDIF
IF TYPE('req.json.precio') = 'N'
REPLACE precio WITH req.json.precio
ENDIF
IF TYPE('req.json.stock') = 'N'
REPLACE stock WITH req.json.stock
ENDIF
* Crear respuesta
loResponse = THIS.newObject("status,message,producto")
loResponse.status = "success"
loResponse.message = "Producto actualizado correctamente"
* Devolver el producto actualizado
res.Status(200).Json(this.ToJson(loResponse))
ENDPROC
* Eliminar un producto
PROCEDURE DeleteProducto(req, res) HELP "DELETE: productos/{id} public"
LOCAL loResponse, lcId
lcId = req.params.id
* Buscar el producto
SELECT productos
LOCATE FOR id = lcId
IF !FOUND()
loResponse = THIS.newObject("status,error,message")
loResponse.status = "error"
loResponse.error = "Producto no encontrado"
loResponse.message = "No se pudo eliminar el producto porque no existe"
res.Status(404).Json(this.ToJson(loResponse))
RETURN
ENDIF
* Eliminar producto
DELETE
loResponse = THIS.newObject("status,message")
loResponse.status = "success"
loResponse.message = "Producto eliminado correctamente"
res.Status(200).Json(this.ToJson(loResponse))
ENDPROC
ENDDEFINE
Importante
Este ejemplo asume que tienes una tabla llamada "productos" con campos id, nombre, precio y stock. Ajusta la estructura según tus necesidades.
Para utilizar los métodos hooks BeforeEndpoint y AfterEndpoint, asegúrate de marcar el check "Hooks" en la creación de tu proyecto.
Ejemplo de JSON para crear un producto
{
"nombre": "Teclado mecánico",
"precio": 89.99,
"stock": 50
}
Ejemplo 3: Maestro-Detalle
Un ejemplo que muestra cómo trabajar con relaciones maestro-detalle para pedidos y sus líneas.
Controlador (PedidosController.prg)
DEFINE CLASS PedidosController AS ApiController OLEPUBLIC
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
* Configuraciones iniciales
SET DELETED ON
SET EXCLUSIVE OFF
SET CENTURY ON
SET TABLEPROMPT OFF
SET SAFETY OFF
SET TALK OFF
* Abrir tablas necesarias
SELECT 0
USE (this.cPath + "data\pedidos") SHARED
SELECT 0
USE (this.cPath + "data\pedido_detalle") SHARED
RETURN .T.
ENDFUNC
PROCEDURE AfterEndpoint(req, res)
* Cerrar tablas
CLOSE DATABASES ALL
ENDPROC
* Obtener un pedido con sus detalles
PROCEDURE GetPedido(req, res) HELP "GET: pedidos/{id} public"
LOCAL loResponse, lcId
lcId = req.params.id
* Obtener el pedido
SELECT * FROM pedidos WHERE id = lcId INTO CURSOR tmpPedido
IF _TALLY = 0
loResponse = THIS.newObject("status,error,message")
loResponse.status = "error"
loResponse.error = "Pedido no encontrado"
loResponse.message = "No se pudo encontrar el pedido con ID " + lcId
res.Status(404).Json(this.ToJson(loResponse))
RETURN
ENDIF
* Obtener los detalles del pedido
SELECT * FROM pedido_detalle WHERE pedido_id = lcId INTO CURSOR tmpDetalles
SELECT tmpPedido
SCATTER MEMO NAME loPedido
* Crear respuesta
loResponse = THIS.newObject("status,pedido,detalles[1],message")
loResponse.status = "success"
loResponse.pedido = loPedido
LOCAL i
i = 0
* Recorremos los detalles y los agregamos al array
SELECT tmpDetalles
SCAN
i = i + 1
SCATTER MEMO NAME loDetalle
loResponse.detalles[i] = loDetalle
ENDSCAN
loResponse.message = "Pedido encontrado correctamente"
res.Status(200).Json(this.ToJson(loResponse))
ENDPROC
* Crear un nuevo pedido con sus detalles
PROCEDURE CreatePedido(req, res) HELP "POST: pedidos public"
LOCAL loResponse, lcPedidoId
IF ISNULL(req.json) OR ISNULL(req.json.cliente) OR ISNULL(req.json.items)
loResponse = THIS.newObject("status,error,message")
loResponse.status = "error"
loResponse.error = "Datos incompletos"
loResponse.message = "Se esperaba un cuerpo JSON con cliente e items"
res.Status(400).Json(this.ToJson(loResponse))
RETURN
ENDIF
* Generar ID para el pedido
lcPedidoId = this.NewGuid()
* Insertar el pedido
SELECT pedidos
APPEND BLANK
REPLACE id WITH lcPedidoId
REPLACE fecha WITH DATE()
REPLACE cliente WITH req.json.cliente
REPLACE total WITH 0
* Insertar los detalles y calcular total
LOCAL lnTotal, i
lnTotal = 0
FOR i = 1 TO ALEN(req.json.items)
LOCAL loItem
loItem = req.json.items[i]
SELECT pedido_detalle
APPEND BLANK
REPLACE pedido_id WITH lcPedidoId
REPLACE linea WITH i
REPLACE producto WITH loItem.producto
REPLACE cantidad WITH loItem.cantidad
REPLACE precio WITH loItem.precio
REPLACE importe WITH loItem.cantidad * loItem.precio
lnTotal = lnTotal + (loItem.cantidad * loItem.precio)
NEXT
* Actualizar total en el pedido
SELECT pedidos
LOCATE FOR id = lcPedidoId
REPLACE total WITH lnTotal
* Devolver el pedido creado
SELECT * FROM pedidos WHERE id = lcPedidoId INTO CURSOR tmpPedido
SELECT * FROM pedido_detalle WHERE pedido_id = lcPedidoId INTO CURSOR tmpDetalles
SELECT tmpPedido
SCATTER MEMO NAME loPedido
loResponse = THIS.newObject("status,pedido,detalles[1],message")
loResponse.status = "success"
loResponse.pedido = loPedido
LOCAL i
i = 0
* Recorremos los detalles y los agregamos al array
SELECT tmpDetalles
SCAN
i = i + 1
SCATTER MEMO NAME loDetalle
loResponse.detalles[i] = loDetalle
ENDSCAN
loResponse.message = "Pedido creado correctamente"
res.Status(201).Json(this.ToJson(loResponse))
ENDPROC
ENDDEFINE
Ejemplo de JSON para crear un pedido
{
"cliente": "C001",
"items": [
{
"producto": "P001",
"cantidad": 2,
"precio": 19.99
},
{
"producto": "P002",
"cantidad": 1,
"precio": 29.99
}
]
}
Estructura de tablas necesarias
-
Tabla pedidos
Campos: id (C), fecha (D), cliente (C), total (N)
-
Tabla pedido_detalle
Campos: pedido_id (C), linea (N), producto (C), cantidad (N), precio (N), importe (N)
Conclusión
Estos ejemplos muestran algunas de las funcionalidades básicas de FoxServer. Para casos más avanzados, consulta la Referencia API o revisa el código fuente del proyecto.
Buenas prácticas
- Utiliza siempre BeforeEndpoint y AfterEndpoint para gestionar recursos como conexiones a bases de datos.
- Devuelve códigos HTTP apropiados según el resultado de la operación.
- Valida siempre los datos de entrada antes de procesarlos.
- Documenta tus endpoints con comentarios claros.