Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eloquent Dynamodb Laravel Package

zaghyr/eloquent-dynamodb

Bring Eloquent-style models to Amazon DynamoDB in Laravel. Define DynamoDB-backed models with familiar syntax, query and persist data without raw AWS SDK calls, and keep application code consistent with the rest of your Eloquent stack.

View on GitHub
Deep Wiki
Context7

Getting Started

v0.3.0 is a dependency upgrade of laravel-dynamodb, not a feature release — there's nothing new to adopt, but you should treat it as a "re-verify" release rather than a no-op.

  1. Pin and diff. composer update baopham/laravel-dynamodb --with-dependencies, then diff composer.lock. The upgrade pulls a transitive aws/aws-sdk-php; a minor/major jump there is the actual risk, not the Laravel wrapper.
  2. Install and register. The service provider is auto-discovered on Laravel 5.5+. On older versions, add BaoPham\DynamoDb\DynamoDbServiceProvider to config/app.php. The facade is BaoPham\DynamoDb\Facades\DynamoDb if you prefer the container-free style.
  3. Publish config. php artisan vendor:publish --provider="BaoPham\DynamoDb\DynamoDbServiceProvider" gives you config/dynamodb.php with key, secret, region, local_endpoint, and debug.
  4. Create the table out of band. The package does not create tables — there are no migrations. Provision the hash key (and optional range key) with the AWS CLI, CloudFormation, or Terraform before writing model code.
  5. Local first. docker run -p 8000:8000 amazon/dynamodb-local, set DYNAMODB_LOCAL_ENDPOINT=http://localhost:8000, and create the table with aws dynamodb create-table --endpoint-url http://localhost:8000 ....
  6. First use case: something accessed strictly by key — user preferences, feature flags per tenant, a session/audit table. Define a model and do CRUD:
class UserPreference extends DynamoDbModel
{
    protected $table = 'user_preferences';
    protected $fillable = ['user_id', 'theme', 'locale'];
    protected $primaryKey = 'user_id';
}

UserPreference::create(['user_id' => 1, 'theme' => 'dark']);
UserPreference::find(1);

Where to look first: the DynamoDbModel and DynamoDbQueryBuilder source in vendor/baopham/laravel-dynamodb/src, and the published config file. Those three files are the entire mental model — treat the README as a summary, not the spec.

Implementation Patterns

Model per table, with composite keys where the access pattern demands it. Use $primaryKey for a hash-only table and $compositeKey = ['pk', 'sk'] when you have a range key. find() takes a scalar or an array for composite keys. Don't try to fight the key design later — DynamoDB key schema is immutable after creation (you'd have to build a new table and migrate).

Design access patterns before models. Write down every read your app performs. Then choose pk/sk so each read is a Query, not a Scan. Single-table design works, but this package's Eloquent veneer actively hides the item-collection semantics — for most teams, one table per entity plus a couple of GSIs is the lower-friction path.

Wrap it in a repository. DynamoDB's constraints (no joins, no lazy loading, cursor pagination, eventually consistent index reads) leak badly into controllers. A thin repository/service per table lets you swap to raw DynamoDbClient calls later without touching controllers, which you will inevitably need.

Be explicit about Query vs Scan. The builder falls back to a Scan when your where clauses don't cover the primary key. That fallback is silent and expensive. Use ->query() / ->scan() / ->onScan() to make intent visible in code review, and log the generated request via ->getQuery() or ->dd() during development.

Cursor pagination, not offset. DynamoDB returns LastEvaluatedKey; there is no page=2. Implement cursor pagination yourself with ExclusiveStartKey, expose the token as an opaque base64 string in an API Resource, and never let the frontend assume stable offsets.

Push batch and conditional operations to the raw client. The model layer is one-item-at-a-time. For bulk seeding, BatchWriteItem (25 items max per call); for idempotency or optimistic locking, UpdateItem with a ConditionExpression such as attribute_not_exists(pk) or version = :expected. updateOrCreate / firstOrCreate are not concurrency-safe here — they read-then-write.

Handle throttling explicitly. Catch Aws\Exception\AwsException, check for ProvisionedThroughputExceededException / RequestLimitExceeded, and retry with jittered exponential backoff. On Lambda or ECS, let the IAM role supply credentials and leave key/secret null.

Test against DynamoDB Local, not a mock. Bind a test DynamoDbClientInterface in the container, or point local_endpoint at the local instance from phpunit.xml. Create and drop tables in setUpBeforeClass / tearDownAfterClass, with a per-run table-name prefix so parallel test runs don't collide.

Use $casts for map/list attributes. DynamoDB natively stores maps and lists, so protected $casts = ['settings' => 'array'] is genuinely useful. Dates are less graceful — store them as ISO-8601 strings or epoch integers explicitly rather than trusting Eloquent's $dates marshalling.

Gotchas and Tips

  • No migrations means schema drift. The most common failure mode is "works locally, fails in prod" because a GSI or key schema differs between environments. Put table definitions in Terraform/CloudFormation from day one.
  • Don't extend Illuminate\Database\Eloquent\Model. You must extend BaoPham\DynamoDb\DynamoDbModel. Relationships, with(), save() cascades, and many model events simply don't exist. If your domain needs joins, DynamoDB is the wrong tool.
  • A where on a non-key attribute is a filter, not a narrowing of the read. You pay for every item read before filtering. Same for Scan filters. Check ConsumedCapacity in CloudWatch after a feature ships.
  • limit applies before filtering. A filtered query can legitimately return zero items even though matching items exist further along. Always loop on LastEvaluatedKey until exhausted or the limit is reached.
  • Consistency. getItem and base-table Query support ConsistentRead; GSI reads and Scan do not. Read-after-write against an index is a classic source of flaky tests and "missing record" bugs.
  • Reserved words. Attribute names like status, size, data, timestamp need ExpressionAttributeNames escaping. The builder may not do it for you — name your attributes defensively or drop to the raw client.
  • Numbers are decimals. Don't store money as a float. Store integer cents.
  • Hard limits: 400 KB per item, 1 MB per Query/Scan page, 25 items per BatchWriteItem, 100 per BatchGetItem. Design for them rather than discovering them at scale.
  • config:cache will bake in local_endpoint and any hard-coded credentials. Guard the config file with env() defaults that are null in production, and verify local_endpoint is absent after config:cache in your deploy pipeline.
  • debug => true logs full request payloads. Never enable it in production — request bodies routinely contain PII.
  • v0.3.0 upgrade checklist: confirm no namespace or method-signature changes in DynamoDbModel/DynamoDbQueryBuilder, check the transitive AWS SDK version delta, run your DynamoDB Local integration suite, and smoke-test one query and one conditional write against a staging table before promoting.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin