Skip to main content
PATCH
/
specialities
/
:id
Update Specialty
curl --request PATCH \
  --url https://api.example.com/specialities/:id \
  --header 'Content-Type: application/json' \
  --data '
{
  "name": "<string>",
  "state": "<string>"
}
'
{
  "speciality": {
    "id": 123,
    "name": "<string>",
    "state": "<string>",
    "createdAt": "<string>",
    "updatedAt": "<string>"
  }
}

Overview

Updates an existing medical specialty in the system. Only administrators can update specialties.

Authentication & Authorization

This endpoint requires both authentication AND administrator privileges.
Required: JWT token from an admin user Middleware chain:
  1. validatorJWT - Validates JWT token (backend/middlewares/validatorJWT.ts)
  2. isAdmin - Verifies user has admin role (backend/middlewares/validatorAdmin.ts)
  3. Validation checks:
    • check("name", "El nombre es obligatorio").not().isEmpty() - Name is required
    • check("name").custom(existNameSpecialityById) - Name must be unique (excluding current record)
    • check("state", "El estado es obligatorio").not().isEmpty() - State is required
  4. collectionErrors - Handles validation errors
Authorization: Admin role required. Non-admin users will receive a 401 or 403 error.

Endpoint

PATCH /specialities/:id
Implementation: backend/controllers/speciality.ts:102

Path Parameters

id
number
required
The unique identifier of the specialty to update.Validation:
  • Must be numeric
  • Must correspond to an existing specialty
Examples:
  • 1 - Updates specialty with ID 1
  • 25 - Updates specialty with ID 25
Validation source: backend/controllers/speciality.ts:110-115

Request Body

name
string
required
The updated name of the medical specialty. Must be unique across the system (excluding the current record).Validation:
  • Cannot be empty
  • Must be unique (validated by existNameSpecialityById helper)
  • Case-sensitive uniqueness check
  • Excludes current record from uniqueness check
Examples:
  • "Cardiología"
  • "Pediatría Avanzada"
  • "Medicina General"
Validation source: backend/routes/speciality.ts:44-45
state
string
required
The state of the specialty.Allowed values:
  • "Activa" - Active specialty
  • "Inactiva" - Inactive specialty
Validation: Must not be empty. Only the two values above are allowed (enforced by database ENUM).Validation source: backend/routes/speciality.ts:46Use cases:
  • Set to "Inactiva" to soft-delete a specialty
  • Set to "Activa" to reactivate a specialty

Request Examples

curl -X PATCH "http://localhost:3000/specialities/1" \
  -H "Authorization: Bearer ADMIN_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Cardiología Avanzada",
    "state": "Activa"
  }'

Response

speciality
object
required
The updated specialty object with current values.

Response Examples

{
  "speciality": {
    "id": 1,
    "name": "Cardiología Avanzada",
    "state": "Activa",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-03-11T15:45:00.000Z"
  }
}

Status Codes

Status CodeDescription
200Success - Specialty successfully updated
400Bad Request - Invalid ID format or validation error
401Unauthorized - Invalid or missing JWT token
403Forbidden - User is not an administrator
404Not Found - Specialty with the given ID does not exist
500Server Error - Database or internal error

Implementation Details

Controller Logic

The update endpoint validates the ID and uses Sequelize’s update method:
export const updateSpeciality = async (req: Request, res: Response) => {
  const data = req.body;
  const id = req.params.id;

  try {
    // Validate ID format
    if (!id || isNaN(Number(id))) {
      res.status(400).json({
        msg: "Debe enviar un ID y debe ser numérico"
      })
      return
    }

    // Check if specialty exists
    const speciality = await Speciality.findByPk(id)

    if (!speciality) {
      res.status(404).json({
        msg: "La especialidad no está cargado en el sistema"
      })
      return
    }

    // Update the specialty
    await speciality.update(data)

    res.status(200).json({
      speciality
    })
  } catch (error) {
    console.log(error)
    res.status(500).json({
      msg: "Error del servidor"
    })       
  }
}
Source: backend/controllers/speciality.ts:102-139

Validation Chain

Validations are defined in the route:
router.patch('/:id',
  [
    validatorJWT,
    isAdmin,
    check("name", "El nombre es obligatorio").not().isEmpty(),
    check("name").custom(existNameSpecialityById),
    check("state", "El estado es obligatorio").not().isEmpty(),
    collectionErrors
  ],
  updateSpeciality
)
Source: backend/routes/speciality.ts:40-50

Uniqueness Check (Excluding Current Record)

The existNameSpecialityById helper validates uniqueness while excluding the current record:
// Helper location: backend/helpers/validationsDB.ts
export const existNameSpecialityById = async (name: string, { req }) => {
  const { id } = req.params;
  
  const speciality = await Speciality.findOne({ 
    where: { 
      name,
      id: { [Op.ne]: id }  // Not equal to current ID
    } 
  });
  
  if (speciality) {
    throw new Error('El nombre de la especialidad ya existe');
  }
}
This allows updating a specialty without changing its name, while still preventing duplicate names.

State Management

States are enforced at the database level using Sequelize ENUM:
state: {
  type: DataTypes.ENUM(...Object.values(STATES_SPECIALITIES)),
  allowNull: false,
  defaultValue: STATES_SPECIALITIES.active
}
Source: backend/models/speciality.ts:27-31 Attempting to set an invalid state value will result in a database error.

Common Use Cases

Update specialty name

const updateSpecialtyName = async (id: number, name: string, currentState: string) => {
  const response = await fetch(`/specialities/${id}`, {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${adminToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name,
      state: currentState  // Keep existing state
    })
  });

  if (!response.ok) {
    throw new Error('Failed to update specialty');
  }

  return await response.json();
};

Deactivate specialty (soft delete)

const deactivateSpecialty = async (id: number, name: string) => {
  const { speciality } = await fetch(`/specialities/${id}`, {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${adminToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name,  // Keep existing name
      state: 'Inactiva'
    })
  }).then(r => r.json());

  console.log(`Deactivated: ${speciality.name}`);
};

Reactivate specialty

const reactivateSpecialty = async (id: number, name: string) => {
  const { speciality } = await fetch(`/specialities/${id}`, {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${adminToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name,  // Keep existing name
      state: 'Activa'
    })
  }).then(r => r.json());

  console.log(`Reactivated: ${speciality.name}`);
};

Update with error handling

try {
  const { speciality } = await fetch(`/specialities/${id}`, {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${adminToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'New Name',
      state: 'Activa'
    })
  }).then(async r => {
    if (!r.ok) {
      const error = await r.json();
      throw new Error(error.msg || 'Update failed');
    }
    return r.json();
  });

  console.log(`Updated specialty: ${speciality.name}`);
} catch (error) {
  if (error.message.includes('ya existe')) {
    console.error('A specialty with this name already exists');
  } else if (error.message.includes('no está cargado')) {
    console.error('Specialty not found');
  } else {
    console.error('Failed to update:', error.message);
  }
}

Notes

Only users with admin role can update specialties. Attempting to call this endpoint without admin privileges will result in a 403 Forbidden error.
Both name and state fields are required in the request body. Omitting either will result in a 400 validation error.

Important Considerations

  • ID Validation: The endpoint validates that the ID is numeric before querying the database
  • Existence Check: The endpoint verifies the specialty exists before attempting to update
  • Uniqueness: The name uniqueness check excludes the current record, allowing you to update other fields without changing the name
  • State Required: Unlike the create endpoint, the state field is required for updates
  • Partial Updates Not Supported: You must provide both name and state fields, even if only updating one
  • Soft Delete Pattern: Setting state to "Inactiva" is the recommended way to “delete” a specialty
  • Timestamps: The updatedAt field is automatically updated by Sequelize
  • Admin Only: The isAdmin middleware enforces that only administrators can update specialties

Soft Delete vs Hard Delete

This API uses a soft delete pattern:
  • Soft Delete: Set state to "Inactiva" (recommended)
    • Preserves historical data
    • Can be reactivated later
    • Maintains referential integrity
  • Hard Delete: Not implemented
    • Would permanently remove the record
    • Could break foreign key constraints
    • Not recommended for production systems

Build docs developers (and LLMs) love